// ProcessTimeDlg.cpp : implementation file
//  Author : YogaRamanan.T

#include "stdafx.h"

#include "psapi.h"  /// PSAPI header to Enumerate Process

#include "ProcessTime.h"
#include "ProcessTimeDlg.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CAboutDlg dialog used for App About

class CAboutDlg : public CDialog
{
public:
	CAboutDlg();

// Dialog Data
	//{{AFX_DATA(CAboutDlg)
	enum { IDD = IDD_ABOUTBOX };
	//}}AFX_DATA

	// ClassWizard generated virtual function overrides
	//{{AFX_VIRTUAL(CAboutDlg)
	protected:
	virtual void DoDataExchange(CDataExchange* pDX);    // DDX/DDV support
	//}}AFX_VIRTUAL

// Implementation
protected:
	//{{AFX_MSG(CAboutDlg)
	//}}AFX_MSG
	DECLARE_MESSAGE_MAP()
};

CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
	//{{AFX_DATA_INIT(CAboutDlg)
	//}}AFX_DATA_INIT
}

void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CAboutDlg)
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
	//{{AFX_MSG_MAP(CAboutDlg)
		// No message handlers
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CProcessTimeDlg dialog

CProcessTimeDlg::CProcessTimeDlg(CWnd* pParent /*=NULL*/)
	: CDialog(CProcessTimeDlg::IDD, pParent)
{
	//{{AFX_DATA_INIT(CProcessTimeDlg)
		// NOTE: the ClassWizard will add member initialization here
	//}}AFX_DATA_INIT
	// Note that LoadIcon does not require a subsequent DestroyIcon in Win32
	m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

void CProcessTimeDlg::DoDataExchange(CDataExchange* pDX)
{
	CDialog::DoDataExchange(pDX);
	//{{AFX_DATA_MAP(CProcessTimeDlg)
		// NOTE: the ClassWizard will add DDX and DDV calls here
	//}}AFX_DATA_MAP
}

BEGIN_MESSAGE_MAP(CProcessTimeDlg, CDialog)
	//{{AFX_MSG_MAP(CProcessTimeDlg)
	ON_WM_SYSCOMMAND()
	ON_WM_PAINT()
	ON_WM_QUERYDRAGICON()
	ON_BN_CLICKED(IDC_BTNPROCESSCLEAR, OnBtnProcessClear)
	ON_BN_CLICKED(IDC_BTNDRIVERSCLEAR, OnBtnDriversClear)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CProcessTimeDlg message handlers

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

	// Add "About..." menu item to system menu.

	// IDM_ABOUTBOX must be in the system command range.
	ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
	ASSERT(IDM_ABOUTBOX < 0xF000);

	CMenu* pSysMenu = GetSystemMenu(FALSE);
	if (pSysMenu != NULL)
	{
		CString strAboutMenu;
		strAboutMenu.LoadString(IDS_ABOUTBOX);
		if (!strAboutMenu.IsEmpty())
		{
			pSysMenu->AppendMenu(MF_SEPARATOR);
			pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
		}
	}

	// 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
	
	// user mode process ListCtrl initialization
	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->InsertColumn(0,"아이디",LVCFMT_RIGHT,60,0);
	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->InsertColumn(1,"순서",LVCFMT_RIGHT,40,1);	
	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->InsertColumn(2,"이름",LVCFMT_LEFT,130,2);
//	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->InsertColumn(3,"시작시간",LVCFMT_RIGHT,120,3);
//	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->InsertColumn(4,"종료시간",LVCFMT_RIGHT,120,4);

	// kernel mode process ListCtrl initialization
//	((CListCtrl*)GetDlgItem(IDC_LSTDRIVER))->InsertColumn(1,"순서",LVCFMT_RIGHT,40,1);
//	((CListCtrl*)GetDlgItem(IDC_LSTDRIVER))->InsertColumn(0,"아이디",LVCFMT_RIGHT,70,0);
//	((CListCtrl*)GetDlgItem(IDC_LSTDRIVER))->InsertColumn(2,"BaseName",LVCFMT_LEFT,100,2);
//	((CListCtrl*)GetDlgItem(IDC_LSTDRIVER))->InsertColumn(3,"FileName",LVCFMT_LEFT,450,3);
	
	int nArr[] = {1,0,2,3,4};

	((CListCtrl*)GetDlgItem(IDC_LSTPROCESS))->SetColumnOrderArray(5,nArr);
	((CListCtrl*)GetDlgItem(IDC_LSTDRIVER))->SetColumnOrderArray(4,nArr);
	
	// create a thread to update the process consecutively
	m_bStopUpdateProcessThread = false;
	m_hUpdateProcessThread = CreateThread(NULL,0,UpdateProcessThread,this,0,NULL);
	
	return TRUE;  // return TRUE  unless you set the focus to a control
}

BOOL CProcessTimeDlg::DestroyWindow() 
{
	m_bStopUpdateProcessThread = true;
	ClearProcessList();
	return CDialog::DestroyWindow();
}

void CProcessTimeDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
	if ((nID & 0xFFF0) == IDM_ABOUTBOX)
	{
		CAboutDlg dlgAbout;
		dlgAbout.DoModal();
	}
	else
	{
		CDialog::OnSysCommand(nID, lParam);
	}
}

// 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 CProcessTimeDlg::OnPaint() 
{
	if (IsIconic())
	{
		CPaintDC dc(this); // device context for painting

		SendMessage(WM_ICONERASEBKGND, (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 to obtain the cursor to display while the user drags
//  the minimized window.
HCURSOR CProcessTimeDlg::OnQueryDragIcon()
{
	return (HCURSOR) m_hIcon;
}

// function to convert FILETIME to meaningful string
BOOL GetFileTimeAsString( LPFILETIME pFt, char * pszTime, unsigned cbIn)
{
	FILETIME ftLocal;
	SYSTEMTIME st;
 
	if(!FileTimeToLocalFileTime( pFt, &ftLocal))
		return FALSE;
 
	if(!FileTimeToSystemTime( &ftLocal, &st))
		return FALSE;
 
	char szTemp[12];
 
	wsprintf(szTemp, "%02u:%02u:%02u", st.wHour, st.wMinute, st.wSecond);
	lstrcpyn(pszTime, szTemp, cbIn); 
	return TRUE;
}

// function to convert FILETIME to meaningful string
BOOL GetFileDateAsString( LPFILETIME pFt, char * pszDate, unsigned cbIn)
{
	FILETIME ftLocal;
	SYSTEMTIME st;
 
	if(!FileTimeToLocalFileTime(pFt, &ftLocal))
		return FALSE;
 
	if(!FileTimeToSystemTime(&ftLocal, &st))
		return FALSE;
 
	char szTemp[12];
 
	wsprintf(szTemp, "%02u/%02u/%04u",	st.wMonth, st.wDay, st.wYear);
	lstrcpyn(pszDate, szTemp, cbIn); 
	return TRUE;
}


// this will update the process start time and end time(end time, only if the process has terminated)
void CProcessTimeDlg::UpdateProcessTime()
{
	CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTPROCESS);
	FILETIME ftCreate, ftExit, ftKernel, ftUser;
    
	int nCount = pList->GetItemCount();

	// loop all the process in the list box
	for(int i=0;i<nCount;i++)
	{
		ST_PROCESSINFO *pstProcessInfo = (ST_PROCESSINFO *)pList->GetItemData(i);
		if(!pstProcessInfo->hProcess)
			continue;

		if(GetProcessTimes(pstProcessInfo->hProcess, &ftCreate, &ftExit, &ftKernel, &ftUser))
		{
			// Horrible, disgusting hack!  The two lines below basically grab the
			// contents of a FILETIME structure and store it in a 64 bit integer.
			LONGLONG tUser64 = *(LONGLONG *)&ftUser;
			LONGLONG tKernel64 = *(LONGLONG *)&ftKernel;
	
			DWORD tUser, tKernel;

			// The LONGLONGs contain the time in 100 nanosecond intervals (now
			// there's a useful unit of measurement...).  Divide each of them by
			// 10000 to convert into milliseconds, and store the results in a
			// DWORD.  This means that the max time before overflowing is around
			// 4 Million seconds (about 49 days)
			tUser = (DWORD)(tUser64 / 10000);
			tKernel = (DWORD)(tKernel64 / 10000);
			
			// Format the user and kernel times, and add to the process node
			char szItem[128];

			char szFileDate[32] = { 0 };
			char szFileTime[32] = { 0 };

			if(!ftCreate.dwHighDateTime&&!ftCreate.dwLowDateTime)
			{
				strcpy(szFileDate,"");
				strcpy(szFileTime,"");
			}
			else
			{
				GetFileDateAsString(&ftCreate, szFileDate, sizeof(szFileDate));
				GetFileTimeAsString(&ftCreate, szFileTime, sizeof(szFileTime));
			}

			wsprintf(szItem, "%s %s", szFileDate, szFileTime);

			CString cszText = pList->GetItemText(i,3);

			// if already exists then don't update, this will reduce the flicker
			if(cszText != szItem)
				pList->SetItemText(i,3,szItem);

			if(!ftExit.dwHighDateTime&&!ftExit.dwLowDateTime)
			{
				strcpy(szFileDate,"");
				strcpy(szFileTime,"");
			}
			else
			{
				GetFileDateAsString(&ftExit, szFileDate, sizeof(szFileDate));
				GetFileTimeAsString(&ftExit, szFileTime, sizeof(szFileTime));
			}

			wsprintf(szItem, "%s %s", szFileDate, szFileTime);

			cszText = pList->GetItemText(i,4);

			// if already exists then don't update, this will reduce the flicker
			if(cszText != szItem)
				pList->SetItemText(i,4,szItem);
		}
	}
}

void CProcessTimeDlg::AddProcessToList(DWORD processID )
{
    //
    // Adds the process name and ID to the ListCtrl
    //

	// first update the process time 
	UpdateProcessTime();

	CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTPROCESS);

	int nCount = pList->GetItemCount();
	
	ST_PROCESSINFO *pstProcessInfo;
	char szBuff[MAX_PATH];
	char szProcessName[MAX_PATH] = "unknown";
	char szItemString[MAX_PATH+64];

	// open the process to query the time information
	//   this handle will remain open untill ClearProcessList call
	//   This should remain open to get the process terminated time 
	HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION|PROCESS_VM_READ,FALSE, processID);
	if(!hProcess)
		return;
	
	for(int i=0;i<nCount;i++)
	{
		ST_PROCESSINFO *pstPrvProcessInfo = (ST_PROCESSINFO *)pList->GetItemData(i);
		
		// If the passed process id is same as the already updated process in the ListCtrl
		//    then check whether it is a terminated process
		//       if not then return immediately without updating (to avoid flicker)
		if(pstPrvProcessInfo->dwProcessId == processID)
		{
			CString cszText = pList->GetItemText(i,4);
			cszText.TrimRight();

			if(cszText == "")
				return;
		}
	}
	
	HMODULE hMod;
	DWORD cbNeeded;	

	if(EnumProcessModules( hProcess, &hMod, sizeof(hMod), &cbNeeded))
		GetModuleBaseName( hProcess, hMod, szProcessName,sizeof(szProcessName));

	wsprintf(szItemString, "%u", processID);
	wsprintf(szBuff,"%d", nCount);

	// fill the structure and store the info for later updates
	pstProcessInfo				= new ST_PROCESSINFO();
	pstProcessInfo->dwProcessId	= processID;
	pstProcessInfo->hProcess	= hProcess;

	pList->InsertItem(nCount,szItemString);
	pList->SetItemText(nCount,2,szProcessName);
	pList->SetItemText(nCount,1,szBuff);

	pList->SetItemData(nCount,(DWORD)pstProcessInfo);
}

// enumerates all the process running in the system
void CProcessTimeDlg::UpdateProcessList()
{
	// Get the list of process IDs
	DWORD aProcesses[1024], cbNeeded;
	if(!EnumProcesses(aProcesses, sizeof(aProcesses), &cbNeeded))
		return;

	// Calculate how many process IDs were returned
	DWORD cProcesses = cbNeeded / sizeof(DWORD);

	// Spit out the information for each ID
	for(int i = 0; i < cProcesses; i++ )
		AddProcessToList(aProcesses[i]);
}

// clears the all the process and closes the handles
void CProcessTimeDlg::ClearProcessList()
{
	CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTPROCESS);

	int nCount = pList->GetItemCount();
	ST_PROCESSINFO *pstProcessInfo;

	for(int i=nCount-1;i>=0;i--)
	{
		pstProcessInfo = (ST_PROCESSINFO *)pList->GetItemData(i);
		delete pstProcessInfo;

		pList->DeleteItem(i);
	}
}

// fill the ListCtrl with drivers loaded 
void CProcessTimeDlg::UpdateDriverList()
{
	CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTDRIVER);

	// Get the list of device driver base addresses
	PVOID aDrivers[1024];
	DWORD cbNeeded;
	if(!EnumDeviceDrivers(aDrivers, sizeof(aDrivers), &cbNeeded))
		return;
    
    // Calculate how many drivers were returned
	DWORD cDrivers = cbNeeded / sizeof(aDrivers[0]);

	// Spit out the information for each driver
	for(unsigned i = 0; i < cDrivers; i++)
	{
		char szBaseName[MAX_PATH]= "";
		char szDriverFileName[MAX_PATH] = "";

		// Get the driver's base name
		if(GetDeviceDriverBaseName(aDrivers[i], szBaseName,sizeof(szBaseName)))
		{
			char szItem[MAX_PATH+64];
			char szBuff[MAX_PATH];

			wsprintf(szItem, "0x%08X",aDrivers[i] );
			wsprintf(szBuff,"%d", i);

			// Get the full path to the driver
			GetDeviceDriverFileName(aDrivers[i], szDriverFileName,sizeof(szDriverFileName));

			int nCount = pList->GetItemCount();

			for(int i=0;i<nCount;i++)
			{
				CString cszText = pList->GetItemText(i,0);
				cszText.TrimRight();
				if(cszText == szItem)
					goto endloop;
			}
			
			pList->InsertItem(i,szItem);
			pList->SetItemText(i,1,szBuff);
			pList->SetItemText(i,2,szBaseName);
			pList->SetItemText(i,3,szDriverFileName);

			pList->SetItemData(i,(DWORD)aDrivers[i]);
endloop:;
		}
	}
}

void CProcessTimeDlg::OnBtnProcessClear() 
{
	ClearProcessList();
}

void CProcessTimeDlg::OnBtnDriversClear() 
{
	CListCtrl *pList = (CListCtrl*)GetDlgItem(IDC_LSTDRIVER);
	pList->DeleteAllItems();
}


// thread calls UpdateProcessList & UpdateDriverList to update the process time
DWORD WINAPI CProcessTimeDlg::UpdateProcessThread(void *lpVoid)
{
	CProcessTimeDlg *pDlg = (CProcessTimeDlg *)lpVoid;

	while(!pDlg->m_bStopUpdateProcessThread)
	{
		pDlg->UpdateProcessList();
		pDlg->UpdateDriverList();
		Sleep(500);
	}
	return 0;
}
