// PatchSFXDlg.cpp : implementation file
//

#include "stdafx.h"
#include "PatchSFX.h"
#include "PatchSFXDlg.h"

#include "ZipArchive.h"
#include "ZipStorage.h"

#include "atlenc.h"
#include <process.h>
#include ".\patchsfxdlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#endif


// CPatchSFXDlg dialog

UINT IID_PROGRESS_EXTRACT_SFXFILES = 1;
UINT PROGRESS_UPDATE_TIME = 200;

void GetCurrentFolderName(CString& szCurrentFolderName)
{
    TCHAR szFullPathName[MAX_PATH * 2];
    GetModuleFileName(NULL, szFullPathName, MAX_PATH * 2 - 1);
    szFullPathName[MAX_PATH * 2 - 1] = 0;

    TCHAR szDrive[MAX_PATH];
    TCHAR szDirectory[MAX_PATH];

    _tsplitpath(szFullPathName, szDrive, szDirectory, 0, 0);
    szCurrentFolderName.Format("%s%s", szDrive, szDirectory);
}

CPatchSFXDlg::CPatchSFXDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CPatchSFXDlg::IDD, pParent)
    , m_szFileProgress(_T("0/0"))
    , m_szPatchFileName(_T(""))
    , m_szInstalledPathKey(_T(""))
    , m_szRegKeyValue(_T(""))
    , m_dwTotalFileSize(0LL)
    , m_dwMinver(0L)
    , m_dwMaxver(0L)
    , m_nTotalFileNum(0)
    , m_hExtractThread(0)
    , m_nExtractThreadID(0)
    , m_szCurrentExtract(_T(""))
{
    m_SharedData.InitSharedData();
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CPatchSFXDlg::DoDataExchange(CDataExchange* pDX)
{
    CDialog::DoDataExchange(pDX);
    DDX_Control(pDX, IDC_EXTRACT_POS, m_edExtractPos);
    DDX_Control(pDX, IDC_EXTRACT_PROGRESS, m_prgExtract);
    DDX_Control(pDX, IDC_BTN_PATCH_NOW, m_btnPatchNow);
    DDX_Control(pDX, IDC_BTN_PATCH_LIST, m_btnPatchAdvanced);
    DDX_Control(pDX, IDC_CONSOLE, m_edLog);
    DDX_Text(pDX, IDC_PROGRESS_FILE, m_szFileProgress);    
    DDX_Text(pDX, IDC_CURRENT_EXTRACT, m_szCurrentExtract);
    DDX_Control(pDX, IDC_BTN_EXTRACT_TO, m_btnExtractPos);
    DDX_Control(pDX, IDC_PATCH_DONE, m_btnPatchDone);
    DDX_Control(pDX, IDCANCEL, m_btnPatchCancel);
}

BEGIN_MESSAGE_MAP(CPatchSFXDlg, CDialog)
	ON_WM_PAINT()
    ON_WM_TIMER()
    ON_WM_CLOSE()
	ON_WM_QUERYDRAGICON()
	
    ON_BN_CLICKED(IDC_BTN_EXTRACT_TO, OnBnClickedBtnExtractTo)
    ON_BN_CLICKED(IDC_BTN_PATCH_NOW, OnBnClickedBtnPatchNow)
    ON_BN_CLICKED(IDC_BTN_PATCH_LIST, OnBnClickedBtnPatchList)

    ON_BN_CLICKED(IDC_PATCH_DONE, OnBnClickedPatchDone)
END_MESSAGE_MAP()


// CPatchSFXDlg message handlers

BOOL CPatchSFXDlg::OnInitDialog()
{
	CDialog::OnInitDialog();

	// Set the icon for this dialog.  The framework does this automatically
	//  when the application's main window is not a dialog
	SetIcon(m_hIcon, TRUE);			// Set big icon
	SetIcon(m_hIcon, FALSE);		// Set small icon

    m_edLog.SetLimitText(UINT_MAX);

    // 자기 파일을 읽어서, 정보를 얻어온다.

    CZipArchive zipFile;
    CString     szError;

    TCHAR szBuff[MAX_PATH * 2];
    if (!GetModuleFileName(NULL, szBuff, MAX_PATH * 2))
    {
        szError.Format(_T("Unable to find patch file : code(%d)"), GetLastError());
        MessageBox(szError, _T("Patch error"), MB_OK | MB_ICONERROR);
        PostQuitMessage(0);
    }
    else
    {
        szBuff[MAX_PATH * 2 - 1] = (TCHAR)0;
        m_szPatchFileName.SetString(szBuff);
    }

    TRY
    {
        zipFile.Open(m_szPatchFileName, CZipArchive::zipOpenReadOnly);
        CZipString szComment = zipFile.GetGlobalComment();
        m_nTotalFileNum =  zipFile.GetCount(true);
        zipFile.Close();

        int nDecodeBufferLen = Base64DecodeGetRequiredLength(szComment.GetLength());
        int nDecodedDataLen  = nDecodeBufferLen;
        LPBYTE lpData = (LPBYTE) malloc(sizeof(BYTE) * nDecodeBufferLen);

        if (!Base64Decode(szComment.GetString(), szComment.GetLength(), lpData, &nDecodedDataLen))
        {
            free(lpData);

            szError.Format(_T("Unable to decode patch header : code(%d)"), GetLastError());
            MessageBox(szError, _T("Patch error"), MB_OK | MB_ICONERROR);
            PostQuitMessage(0);
        }
        else
        {
            CMemFile srcData(lpData, nDecodeBufferLen);            

            srcData.Read(&m_dwTotalFileSize, sizeof(m_dwTotalFileSize));
            srcData.Read(&m_dwMinver, sizeof(m_dwMinver));
            srcData.Read(&m_dwMaxver, sizeof(m_dwMaxver));

            int nInstalledPathLen = 0;
            int nValueNameLen = 0;

            TCHAR szInstalledPathKey[MAX_PATH];
            TCHAR szRegKeyValue[MAX_PATH];

            srcData.Read(&nInstalledPathLen, sizeof(nInstalledPathLen));
            srcData.Read(&nValueNameLen, sizeof(nValueNameLen));

            srcData.Read(szInstalledPathKey, sizeof(TCHAR) * nInstalledPathLen);
            m_szInstalledPathKey.SetString(szInstalledPathKey, nInstalledPathLen);

            srcData.Read(szRegKeyValue, sizeof(TCHAR) * nValueNameLen);
            m_szRegKeyValue.SetString(szRegKeyValue, nValueNameLen);
        }
    }
    CATCH_ALL(e)
    {
        DWORD dwError = GetLastError();

        if (e->IsKindOf(RUNTIME_CLASS(CZipException)))
        {
            szError.Format(_T("Read Patch Header From Archive Error : code(%d)"), dwError);
        }
        else
        {
            szError.Format(_T("Invalid Patch Header : code(%d)"), dwError);
        }

        MessageBox(szError, _T("Patch error"), MB_OK | MB_ICONERROR);
        PostQuitMessage(0);
    }
    END_CATCH_ALL;
	

    CRegKey regKey;
    TCHAR   szInstalledBuffer[MAX_PATH * 2];
    ULONG   nBufferSize = MAX_PATH * 2 - 1;

    if (ERROR_SUCCESS == regKey.Open(HKEY_LOCAL_MACHINE, m_szInstalledPathKey, KEY_READ) &&
        ERROR_SUCCESS == regKey.QueryStringValue(m_szRegKeyValue, szInstalledBuffer, &nBufferSize))
    {
        szInstalledBuffer[MAX_PATH * 2 - 1] = (TCHAR)0;

        CString szInstalledPath(szInstalledBuffer);
        szInstalledPath.Trim();

        int nLength = szInstalledPath.GetLength();
        if (1 < nLength && szInstalledPath.GetString()[nLength - 1] != _T('\\'))
        {
            szInstalledPath.Append(_T("\\"));
        }
        
        m_edExtractPos.SetWindowText(szInstalledPath);
        UpdateData(FALSE);
    }

    CString szWindowTitle;
/*
    if (m_dwMaxver < 1000)
    {
        szWindowTitle.Format(_T("RYL Part1 Patch (version %d to %d)"), 
            m_dwMinver + 100, m_dwMaxver + 100);
    }
    else
    {
        szWindowTitle.Format(_T("RYL Part2 Patch (version %d to %d)"), 
            m_dwMinver, m_dwMaxver);
    }
*/
    szWindowTitle.Format(_T("Return of Warrior Patch (version %d to %d)"), 
        m_dwMinver, m_dwMaxver);

    SetWindowText(szWindowTitle);
	return TRUE;  // return TRUE  unless you set the focus to a control
}

// If you add a minimize button to your dialog, you will need the code below
//  to draw the icon.  For MFC applications using the document/view model,
//  this is automatically done for you by the framework.

void CPatchSFXDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, 
            reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

		// Center icon in client rectangle
		int cxIcon = GetSystemMetrics(SM_CXICON);
		int cyIcon = GetSystemMetrics(SM_CYICON);
		CRect rect;
		GetClientRect(&rect);
		int x = (rect.Width() - cxIcon + 1) / 2;
		int y = (rect.Height() - cyIcon + 1) / 2;

		// Draw the icon
		dc.DrawIcon(x, y, m_hIcon);
	}
	else
	{
		CDialog::OnPaint();
	}
}

// The system calls this function to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CPatchSFXDlg::OnQueryDragIcon()
{
	return static_cast<HCURSOR>(m_hIcon);
}

void CPatchSFXDlg::OnBnClickedBtnExtractTo()
{
    CString szFolderName;

    BROWSEINFO brInfo;
    memset(&brInfo, 0, sizeof(BROWSEINFO));

    TCHAR szTemp[MAX_PATH * 2];

    brInfo.hwndOwner        = GetSafeHwnd();
    brInfo.pidlRoot         = NULL; 
    brInfo.pszDisplayName   = szTemp;
    brInfo.lpszTitle        = _T("Select patch extract folder");
    brInfo.ulFlags          = BIF_USENEWUI | BIF_RETURNONLYFSDIRS;

    LPITEMIDLIST lpItemIDList = SHBrowseForFolder(&brInfo);
    if (NULL != lpItemIDList && SHGetPathFromIDList(lpItemIDList, szTemp))
    {
        szTemp[MAX_PATH * 2 - 1] = (TCHAR)0;
        szFolderName.SetString(szTemp);
        szFolderName.Trim();

        // 경로 설정이므로, 맨 마지막 글자가 역슬래쉬로 끝나지 않을 경우, 역슬래쉬를 붙여 준다.
        int nLength = szFolderName.GetLength();
        if (1 <  nLength && _T('\\') != szFolderName.GetString()[nLength - 1])
        {
            szFolderName.Append(_T("\\"));
        }
    }

    if (!szFolderName.IsEmpty())
    {
        m_edExtractPos.SetWindowText(szFolderName);

        m_btnPatchDone.ShowWindow(SW_HIDE);
        m_btnPatchNow.ShowWindow(SW_SHOW);
        m_btnPatchCancel.ShowWindow(SW_SHOW);

        UpdateData(FALSE);
    }
}


struct ExtractWorkerData
{
    CPatchSFXDlg*   m_lpDlg;    
    CString         m_szExtractPos;
    DWORD           m_dwMinver;
    DWORD           m_dwMaxver;
};


unsigned __stdcall CPatchSFXDlg::ExtractWorker(void *pArg)
{
    ExtractWorkerData* lpWorkerData = 
        reinterpret_cast<ExtractWorkerData*>(pArg);

    CPatchSFXDlg* lpDlg = lpWorkerData->m_lpDlg;

    SharedData sharedData;

    TCHAR szFileName[MAX_PATH * 2];
    GetModuleFileName(NULL, szFileName, MAX_PATH * 2);
    szFileName[MAX_PATH * 2 - 1] = 0;

    CZipArchive     zipFile;
    CZipFileHeader  fhInfo;

    CString szError;

    const int MAX_ERROR_LEN = 256;
    TCHAR   szErrorMsg[MAX_ERROR_LEN];

    bool bFailedExtract = false;

    TRY
    {
        zipFile.Open(szFileName, CZipArchive::zipOpenReadOnly);
        int nTotalFileNum = zipFile.GetCount();
        zipFile.SetRootPath(lpWorkerData->m_szExtractPos);
        
        for (; !sharedData.m_bStopWorkerThread && sharedData.m_nCurrentFiles < nTotalFileNum; 
            ++sharedData.m_nCurrentFiles)
        {
            if (!zipFile.GetFileInfo(fhInfo, sharedData.m_nCurrentFiles))
            {
                szError.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
                szError.AppendFormat(_T("Get fileinfo failed : index(%d)"), 
                    sharedData.m_nCurrentFiles);

                sharedData.m_ProgressList.AddTail(szError);

                bFailedExtract = true;            
            }
            else
            {
                sharedData.m_szCurrentExtract.SetString(
                    zipFile.PredictExtractedFileName(fhInfo.GetFileName(), 
                    lpWorkerData->m_szExtractPos, true));
                
                // 파일이 있으면 속성을 Normal로
                SetFileAttributes(sharedData.m_szCurrentExtract, 
                    FILE_ATTRIBUTE_NORMAL | FILE_ATTRIBUTE_ARCHIVE);

                lpDlg->UpdateProgress(sharedData);

                if (!zipFile.ExtractFile(sharedData.m_nCurrentFiles, lpWorkerData->m_szExtractPos))
                {
                    szError.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
                    szError.AppendFormat(_T("Extract file failed! : %s%s"), 
                        lpWorkerData->m_szExtractPos, fhInfo.GetFileName());

                    sharedData.m_ProgressList.AddTail(szError);

                    bFailedExtract = true;
                }
                
                sharedData.m_nCurrentDataSize += fhInfo.m_uUncomprSize;
            }
            
            lpDlg->UpdateProgress(sharedData);
        }

        szError.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));

        if (sharedData.m_bStopWorkerThread)
        {
            szError.AppendFormat(_T("Canceled extract patch."));
        }
        else if (bFailedExtract)
        {
            szError.AppendFormat(_T("Failed to extract patch."));
        }
        else
        {
            CString szVersionInfoFileName;
            CString szCurrentVersion;

            szVersionInfoFileName.Format(_T("%sVersionInfo.dat"), 
                lpWorkerData->m_szExtractPos);

            szCurrentVersion.Format(_T("%d"), lpWorkerData->m_dwMaxver);

            CStdioFile file;            
            if (!file.Open(szVersionInfoFileName, 
                CFile::modeCreate | CFile::modeWrite | CFile::typeText))
            {
                szError.AppendFormat(_T("Failed to write version info!"));                
            }
            else
            {
                file.WriteString(szCurrentVersion);
                szError.AppendFormat(_T("Patch Complete! : %s"), lpWorkerData->m_szExtractPos);
            }
        }

        sharedData.m_ProgressList.AddTail(szError);
        zipFile.Close();
    }
    CATCH_ALL(e)
    {
        e->GetErrorMessage(szErrorMsg, MAX_ERROR_LEN - 1);
        szErrorMsg[MAX_ERROR_LEN - 1] = 0;

        if (e->IsKindOf(RUNTIME_CLASS(CZipException)))
        {
            szError.Format(_T("Extract failed : zipArchive error! (%s)"), szErrorMsg);
        }
        else
        {
            szError.Format(_T("Extract failed : unknown error! (%s)"), szErrorMsg);
        }

        sharedData.m_ProgressList.AddTail(szError);
    }
    END_CATCH_ALL;
    
    delete lpWorkerData;
    lpDlg->UpdateProgress(sharedData);

    return 0;
}

void CPatchSFXDlg::UpdateProgress(SharedData& sharedData)
{
    m_csProgress.Lock();

    sharedData.m_bStopWorkerThread      = m_SharedData.m_bStopWorkerThread;

    m_SharedData.m_nCurrentFiles        = sharedData.m_nCurrentFiles;
    m_SharedData.m_nCurrentDataSize     = sharedData.m_nCurrentDataSize;
    m_SharedData.m_szCurrentExtract     = sharedData.m_szCurrentExtract;

    m_SharedData.m_ProgressList.AddTail(&sharedData.m_ProgressList);
    
    m_csProgress.Unlock();

    sharedData.m_ProgressList.RemoveAll();
}


afx_msg void CPatchSFXDlg::OnTimer(UINT_PTR nIDEvent)
{
    if (nIDEvent == IID_PROGRESS_EXTRACT_SFXFILES)
    {
        // 데이터를 갱신하고, 스레드가 종료되었는지를 검출한다. 

        int nLower = 0;
        int nUpper = 0;
        m_prgExtract.GetRange(nLower, nUpper);

        int nCurrentPos = m_prgExtract.GetPos();

        // 락을 건다
        m_csProgress.Lock();

        if (nUpper != static_cast<int>(m_dwTotalFileSize))
        {
            // 범위가 바뀌면 범위 설정
            m_prgExtract.SetRange32(0, static_cast<int>(m_dwTotalFileSize));
        }

        if (nCurrentPos != m_SharedData.m_nCurrentDataSize)
        {
            // 현재 위치 설정
            m_prgExtract.SetPos(m_SharedData.m_nCurrentDataSize);
        }

        m_szCurrentExtract.SetString(m_SharedData.m_szCurrentExtract);

        m_szFileProgress.Format(_T("%d/%d"), 
            m_SharedData.m_nCurrentFiles, m_nTotalFileNum);

        // 로그 메시지를 화면에 뿌린다.
        POSITION pos = m_SharedData.m_ProgressList.GetHeadPosition();
        while(0 != pos) { m_edLog.AddLine(m_SharedData.m_ProgressList.GetNext(pos)); }
        m_SharedData.m_ProgressList.RemoveAll();

        // 락을 해제한다.
        m_csProgress.Unlock();

        UpdateData(FALSE);

        if (WAIT_OBJECT_0 == WaitForSingleObject(m_hExtractThread, 0))
        {
            StopWorker();

            if (!m_PatchFolderList.IsEmpty())
            {
                m_edExtractPos.SetWindowText(m_PatchFolderList.RemoveHead());
                UpdateData(FALSE);

                // 패치리스트에 패치가 남아 있으면, 또 패치를 한다..
                PostMessage(WM_COMMAND, MAKEWPARAM(IDC_BTN_PATCH_NOW, BN_CLICKED));
            }
            else
            {
                m_btnPatchDone.ShowWindow(SW_SHOW);
                m_btnPatchNow.ShowWindow(SW_HIDE);
                m_btnPatchCancel.ShowWindow(SW_HIDE);
            }
        }
    }
}

bool CPatchSFXDlg::CheckExtractPosVersion()
{
    // 파일 위치에 VersionInfo.dat파일이 있나 살펴본 뒤에, 있으면 최소버전 / 최대버전 사이의 값인지 살핀다.
    // 만일 파일이 존재하고, 버전이 범위 밖의 값이면, 강제로 패치할 지 물어본다.    

    UpdateData(TRUE);

    CString szError;
    CString szVersion;
    CString szExtractPos;    
    CString szVersionInfoFileName;

    m_edExtractPos.GetWindowText(szExtractPos);
    szVersionInfoFileName.Format(_T("%sVersionInfo.dat"), szExtractPos);
    
    CStdioFile file;
    if (file.Open(szVersionInfoFileName, CFile::modeRead | CFile::typeText))
    {
        file.ReadString(szVersion);

        DWORD dwVersion = _ttol(szVersion);

        if (dwVersion < m_dwMinver)
        {
            MessageBox(_T("Current version is too low, try other patch first"), 
                _T("Patch error"), MB_OK | MB_ICONERROR);

            return false;
        }
        else if (m_dwMaxver < dwVersion &&
            IDNO == MessageBox(_T("Current version is higher than this patch version. Overwrite it?"),
            _T("Question"), MB_YESNO | MB_ICONEXCLAMATION))
        {
            return false;
        }
    }

    return true;
}

void CPatchSFXDlg::OnBnClickedBtnPatchNow()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.

    UpdateData(TRUE);

    CString szError;
    CString szPatchFolder;
    bool bFailedAndCleanup = true;
    
    ExtractWorkerData* lpWorkerData = new ExtractWorkerData;

    if (0 != lpWorkerData)
    {
        m_edExtractPos.GetWindowText(szPatchFolder);

        lpWorkerData->m_lpDlg = this;
        lpWorkerData->m_dwMinver = m_dwMinver;
        lpWorkerData->m_dwMaxver = m_dwMaxver;
        lpWorkerData->m_szExtractPos.SetString(szPatchFolder);

        if (0 != m_hExtractThread)
        {
            // 스레드가 이미 동작중임. 원래 버튼을 누를 수 없으니
            // 가볍게 씹어줍시다.
        }
        else if (lpWorkerData->m_szExtractPos.IsEmpty())
        {
            MessageBox(_T("Invalid Extract Position. Please set extract position"), 
                _T("Patch Error"), MB_OK | MB_ICONERROR);
            PostMessage(WM_COMMAND, MAKEWPARAM(IDC_BTN_EXTRACT_TO, BN_CLICKED));
        }
        else if (CheckExtractPosVersion())
        {               
            m_hExtractThread = reinterpret_cast<HANDLE>(
                _beginthreadex(0, 0, ExtractWorker, lpWorkerData, 0, &m_nExtractThreadID));

            if (0 == m_hExtractThread)
            {                
                szError.Format(_T("Create extract thread failed : code(%d)"), GetLastError());
                MessageBox(szError, _T("Patch Error"), MB_OK | MB_ICONERROR);
            }
            else
            {
                // 스레드 생성 성공
                bFailedAndCleanup = false;

                // 화면을 갱신하도록 타이머를 세팅한다.
                SetTimer(IID_PROGRESS_EXTRACT_SFXFILES, PROGRESS_UPDATE_TIME, NULL);

                // 패치 버튼을 더 못 누르도록 한다.
                m_btnPatchNow.EnableWindow(FALSE);
                m_btnPatchAdvanced.EnableWindow(FALSE);
                m_btnExtractPos.EnableWindow(FALSE);
            }
        }
    }

    if (bFailedAndCleanup)
    {
        delete lpWorkerData;
    }
}

void CPatchSFXDlg::StopWorker()
{
    if (0 != m_hExtractThread)
    {
        m_csProgress.Lock();
        m_SharedData.m_bStopWorkerThread = TRUE;
        m_csProgress.Unlock();

        WaitForSingleObject(m_hExtractThread, INFINITE);

        KillTimer(IID_PROGRESS_EXTRACT_SFXFILES);

        // 스레드가 종료되었음.
        CloseHandle(m_hExtractThread);
        m_hExtractThread = 0;
        m_nExtractThreadID = 0;
        m_SharedData.InitSharedData();

        m_btnPatchNow.EnableWindow(TRUE);
        m_btnPatchAdvanced.EnableWindow(TRUE);
        m_btnExtractPos.EnableWindow(TRUE);
    }
}

void CPatchSFXDlg::OnClose()
{
    // TODO: 여기에 메시지 처리기 코드를 추가 및/또는 기본값을 호출합니다.
    m_PatchFolderList.RemoveAll();

    StopWorker();
    CDialog::OnClose();
}

void CPatchSFXDlg::OnBnClickedBtnPatchList()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    
    // 패치리스트 파일을 찾아서 읽는다.
    // 파일에서 폴더리스트를 읽는다.
    
    // 첫번째 폴더를 에딧박스에 세팅한다.
    // 패치 버튼을 누르게 한다.

    CString szLog;
    CString szPatchFolder;
    CString szFolderListFile;
    GetCurrentFolderName(szFolderListFile);

    szFolderListFile.AppendFormat(_T("PatchFolders.ini"));

    CStdioFile folderList;

    if (folderList.Open(szFolderListFile, 
        CFile::modeCreate | CFile::modeNoTruncate | CFile::modeRead | CFile::shareDenyNone | CFile::typeText))
    {
        while(folderList.ReadString(szPatchFolder))
        {
            szPatchFolder.Trim();
            szPatchFolder.Replace(_T('/'), _T('\\'));

            int nLength = szPatchFolder.GetLength();
            if (1 < nLength && szPatchFolder.GetString()[nLength - 1] != _T('\\'))
            {
                szPatchFolder.Append(_T("\\"));
            }

            if (INVALID_FILE_ATTRIBUTES != GetFileAttributes(szPatchFolder))
            {
                m_PatchFolderList.AddTail(szPatchFolder);
            }
            else
            {
                szLog.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
                szLog.AppendFormat(_T("Invalid folder %s : Please write existing folder"),
                    szPatchFolder);

                m_edLog.AddLine(szLog);
            }
        }

        if (!m_PatchFolderList.IsEmpty())
        {
            POSITION pos = m_PatchFolderList.GetHeadPosition();

            while (0 != pos)
            {
                CString& szReadyPatchFolder = m_PatchFolderList.GetNext(pos);

                szLog.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
                szLog.AppendFormat(_T("Ready to patch %s folder now!"), szReadyPatchFolder);

                m_edLog.AddLine(szLog);
            }

            m_btnPatchDone.ShowWindow(SW_HIDE);
            m_btnPatchNow.ShowWindow(SW_SHOW);
            m_btnPatchCancel.ShowWindow(SW_SHOW);

            m_edExtractPos.SetWindowText(m_PatchFolderList.RemoveHead());
            UpdateData(FALSE);

            // 패치리스트에 패치가 남아 있으면, 또 패치를 한다..
            PostMessage(WM_COMMAND, MAKEWPARAM(IDC_BTN_PATCH_NOW, BN_CLICKED));
        }
        else
        {
            szLog.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
            szLog.AppendFormat(_T("There is no folder list in %s file\nPlease edit contents"), 
                szFolderListFile);
            MessageBox(szLog, "Patch Error", MB_OK | MB_ICONEXCLAMATION);
        }
    }
    else
    {
        szLog.SetString(CTime::GetCurrentTime().Format(_T("[%H:%M:%S]")));
        szLog.AppendFormat(_T("Open %s file failed\nPlease edit contents"), szFolderListFile);
        MessageBox(szLog, "Patch Error", MB_OK | MB_ICONEXCLAMATION);
    }
}

void CPatchSFXDlg::OnBnClickedPatchDone()
{
    // TODO: 여기에 컨트롤 알림 처리기 코드를 추가합니다.
    PostQuitMessage(0);
}
