//
// ParseInf.cpp
//
//		Code that parses network INF files
//
// History:
//
//		 ?/??/1999  KenSh     Created for JetNet
//		 9/29/1999  KenSh     Repurposed for Home Networking Wizard
//

#include "stdafx.h"
#include "ParseInf.h"
#include "SortStr.h"
#include "Registry.h"


#define SECTION_BUFFER_SIZE		(32 * 1024)

// Non-localized strings
#define SZ_INF_BACKUP_SUFFIX ".inf (HNW backup)"
#define SZ_MODIFIED_INF_HEADER "; Modified by Home Networking Wizard\r\n" \
							   "; Original version backed up to \""
#define SZ_MODIFIED_INF_HEADER2 "\"\r\n"
#define SZ_CHECK_MODIFIED_HEADER "; Modified by Home Networking Wizard"


//////////////////////////////////////////////////////////////////////////////
// Utility functions

int GetInfDirectory(LPTSTR pszBuf, int cchBuf, BOOL bAppendBackslash)
{
    CRegistry regWindows;
    int cch = 0;
    if (regWindows.OpenKey(HKEY_LOCAL_MACHINE, "Software\\Microsoft\\Windows\\CurrentVersion", KEY_QUERY_VALUE))
    {
        cch = regWindows.QueryStringValue("DevicePath", pszBuf, cchBuf);
    }

    // Fill in a default if the reg key is missing
    // REVIEW: Is this likely enough that we should even bother?
    if (cch == 0)
    {
        ASSERT(cchBuf > 8);
        cch = GetWindowsDirectory(pszBuf, cchBuf - 4);
        if (0!=cch)
        {
            if (pszBuf[cch-1] != '\\')
                pszBuf[cch++] = '\\';
        }
        lstrcpy(pszBuf + cch, "INF");
        cch += 3;
    }

    if (bAppendBackslash)
    {
        if (pszBuf[cch-1] != '\\')
        {
            pszBuf[cch++] = '\\';
            pszBuf[cch] = '\0';
        }
    }

    return cch;
}

int GetFullInfPath(LPCTSTR pszPartialPath, LPTSTR pszBuf, int cchBuf)
{
	if (IsFullPath(pszPartialPath))
	{
		lstrcpyn(pszBuf, pszPartialPath, cchBuf);
	}
	else
	{
		int cch = GetInfDirectory(pszBuf, cchBuf, TRUE);
		lstrcpyn(pszBuf + cch, pszPartialPath, cchBuf - cch);
	}

	return lstrlen(pszBuf);
}

int AddCommaSeparatedValues(const CStringArray& rgTokens, CStringArray& rgValues, BOOL bIgnoreInfSections)
{
	int cAdded = 0;

	for (int iToken = 2; iToken < rgTokens.GetSize(); iToken++)
	{
		CString& strTok = ((CStringArray&)rgTokens).ElementAt(iToken);
		if (strTok.Compare(",") == 0)
			continue;
		if (strTok.Compare(";") == 0)
			break;

		// Hack: ignore sections whose name ends in ".inf"
		if (bIgnoreInfSections)
		{
			if (0 == lstrcmpi(FindExtension(strTok), "inf"))
				continue;
		}

		rgValues.Add(strTok);
		cAdded++;
	}

	return cAdded;
}

// Builds a list of all files that need to be copied for the device
BOOL GetDeviceCopyFiles(CInfParser& parser, LPCTSTR pszDeviceID, CDriverFileArray& rgDriverFiles)
{
	if (!parser.GotoSection("Manufacturer"))
		return FALSE;

	CStringArray rgMfr;
	CStringArray rgLineTokens;
	while (parser.GetSectionLineTokens(rgLineTokens))
	{
		if (rgLineTokens.GetSize() >= 3 && rgLineTokens.ElementAt(1).Compare("=") == 0)
			rgMfr.Add(rgLineTokens.ElementAt(2));
	}

	CString strNdiSection;

	// Look in each manufacturer section (e.g. "[3COM]") for the given DeviceID
	for (int iMfr = 0; iMfr < rgMfr.GetSize(); iMfr++)
	{
		if (!parser.GotoSection(rgMfr[iMfr]))
			continue;

		while (parser.GetSectionLineTokens(rgLineTokens))
		{
			if (rgLineTokens.GetSize() >= 5 && 
				rgLineTokens.ElementAt(1).Compare("=") == 0 &&
				rgLineTokens.ElementAt(3).Compare(",") == 0)
			{
				if (rgLineTokens.ElementAt(4).CompareNoCase(pszDeviceID) == 0)
				{
					strNdiSection = rgLineTokens.ElementAt(2);
					break;
				}
			}
		}

		if (!strNdiSection.IsEmpty())
			break;
	}

	if (strNdiSection.IsEmpty())
		return FALSE;

	CStringArray rgCopySections;
	CStringArray rgAddRegSections;

	// Look in [DeviceID.ndi] section for AddReg= and CopyFiles=
	if (!parser.GotoSection(strNdiSection))
		return FALSE;
	while (parser.GetSectionLineTokens(rgLineTokens))
	{
		if (rgLineTokens.GetSize() >= 3 &&
			rgLineTokens.ElementAt(1).Compare("=") == 0)
		{
			CString& strKey = rgLineTokens.ElementAt(0);
			CString& strValue = rgLineTokens.ElementAt(2);
			if (strKey.CompareNoCase("AddReg") == 0)
			{
				AddCommaSeparatedValues(rgLineTokens, rgAddRegSections, FALSE);
			}
			else if (strKey.CompareNoCase("CopyFiles") == 0)
			{
				AddCommaSeparatedValues(rgLineTokens, rgCopySections, FALSE);
			}
		}
	}

	// Look through AddReg sections for HKR,Ndi\Install,,,"DeviceID.Install"
	for (int iAddReg = 0; iAddReg < rgAddRegSections.GetSize(); iAddReg++)
	{
		if (!parser.GotoSection(rgAddRegSections[iAddReg]))
			continue;

		while (parser.GetSectionLineTokens(rgLineTokens))
		{
			if (rgLineTokens.GetSize() >= 7 &&
				rgLineTokens.ElementAt(0).CompareNoCase("HKR") == 0 &&
				rgLineTokens.ElementAt(1).Compare(",") == 0 &&
				rgLineTokens.ElementAt(2).CompareNoCase("Ndi\\Install") == 0 &&
				rgLineTokens.ElementAt(3).Compare(",") == 0)
			{
				// Pull out the 5th comma-separated string, and pull the quotes off
				int iSection = 2;
				for (int iToken = 4; iToken < rgLineTokens.GetSize(); iToken++)
				{
					CString& strTok = rgLineTokens.ElementAt(iToken);
					if (strTok.Compare(";") == 0)
						break;

					if (strTok.Compare(",") == 0)
					{
						iSection++;
						continue;
					}

					if (iSection == 4)
					{
						CString strSection = strTok;
						if (strSection[0] == '\"')
							strSection = strSection.Mid(1, strSection.GetLength() - 2);
						rgCopySections.Add(strSection);
						break;
					}
				}
			}
		}
	}

	// Look in [DeviceID.Install], etc., sections for CopyFiles= lines
	for (int iCopyFiles = 0; iCopyFiles < rgCopySections.GetSize(); iCopyFiles++)
	{
		parser.GetFilesFromInstallSection(rgCopySections[iCopyFiles], rgDriverFiles);
	}

	parser.GetFilesFromCopyFilesSections(rgCopySections, rgDriverFiles);

	return TRUE;
}

BOOL GetDeviceCopyFiles(LPCTSTR pszInfFileName, LPCTSTR pszDeviceID, CDriverFileArray& rgDriverFiles)
{
	CInfParser parser;
	if (!parser.LoadInfFile(pszInfFileName))
		return FALSE;
	return GetDeviceCopyFiles(parser, pszDeviceID, rgDriverFiles);
}

CDriverFileArray::~CDriverFileArray()
{
	for (int i = 0; i < GetSize(); i++)
	{
		free((DRIVER_FILE_INFO*)GetAt(i));
	}
}


//////////////////////////////////////////////////////////////////////////////
// CInfParser

CInfParser::CInfParser()
{
	m_pszFileData = NULL;
}

CInfParser::~CInfParser()
{
	free(m_pszFileData);
}

BOOL CInfParser::LoadInfFile(LPCTSTR pszInfFile, LPCTSTR pszSeparators)
{
	TCHAR szInfFile[MAX_PATH];
	GetFullInfPath(pszInfFile, szInfFile, _countof(szInfFile));

	free(m_pszFileData);
	m_pszFileData = (LPSTR)LoadFile(szInfFile, &m_cbFile);
	m_iPos = 0;

	m_strSeparators = pszSeparators;
	m_strExtSeparators = pszSeparators;
	m_strExtSeparators += " \t\r\n";

	m_strFileName = pszInfFile;

	return (BOOL)m_pszFileData;
}

BOOL CInfParser::Rewind()
{
	ASSERT(m_pszFileData != NULL);
	m_iPos = 0;
	return (BOOL)m_pszFileData;
}

BOOL CInfParser::GotoNextLine()
{
	ASSERT(m_pszFileData != NULL);

	for (LPTSTR pch = m_pszFileData + m_iPos; *pch != '\0' && *pch != '\r' && *pch != '\n'; pch++)
		NULL;

	if (*pch == '\r')
		pch++;
	if (*pch == '\n')
		pch++;

	DWORD iPos = (DWORD)(pch - m_pszFileData);
	if (iPos == m_iPos)
		return FALSE; // we were already at EOF

	m_iPos = iPos;
	return TRUE;
}

BOOL CInfParser::GetToken(CString& strTok)
{
	strTok.Empty();

	if (m_pszFileData == NULL)
	{
		ASSERT(FALSE);
		return FALSE;
	}

	LPTSTR pch = m_pszFileData + m_iPos;
	TCHAR ch;
	BOOL bQuoted = FALSE;

	// Skip whitespace
	while ((ch = *pch) == ' ' || ch == '\t')
		pch++;

	if (ch == '\0')
		goto done;

	// Check for linebreak
	if (ch == '\r' || ch == '\n')
	{
		strTok = ch;
		pch++;
		if (ch == '\r' && *pch == '\n')
		{
			strTok += '\n';
			pch++;
		}
		goto done;
	}

	// Check for separator
	if (NULL != strchr(m_strSeparators, ch))
	{
		strTok = ch;
		pch++;
		goto done;
	}

	LPTSTR pszStart;
	for (pszStart = pch; (ch = *pch) != '\0'; pch++)
	{
		if (!bQuoted && NULL != strchr(m_strExtSeparators, ch))
		{
			break;
		}
		else if (ch == '\"')
		{
			bQuoted = !bQuoted;
		}
	}

	if (pch != pszStart)
	{
		DWORD cch = (DWORD)(pch - pszStart);
		LPTSTR pszToken = strTok.GetBufferSetLength(cch);
		lstrcpyn(pszToken, pszStart, cch+1);
	}

done:
	m_iPos = (DWORD)(pch - m_pszFileData);
	return (BOOL)strTok.GetLength();
}

BOOL CInfParser::GetLineTokens(CStringArray& sa)
{
	CString strToken;
	BOOL bResult = FALSE;

	sa.RemoveAll();
	while (GetToken(strToken))
	{
		bResult = TRUE; // not at EOF
		if (strToken[0] == '\r' || strToken[0] == '\n')
			break;
		sa.Add(strToken);
	}

	return bResult;
}

BOOL CInfParser::GetSectionLineTokens(CStringArray& sa)
{
begin:
	if (!GetLineTokens(sa))
		return FALSE;

	if (sa.GetSize() == 0)
		goto begin;

	CString& strFirst = sa.ElementAt(0);

	if (strFirst[0] == '[')			// end of section
		return FALSE;

	if (strFirst.Compare(";") == 0)	// comment
	{
		sa.RemoveAll();
		goto begin;
	}

	return TRUE;
}

BOOL CInfParser::GotoSection(LPCTSTR pszSection)
{
	CString strSection;
	CString strToken;

	if (!Rewind())
		return FALSE;

	TCHAR ch;
	BOOL bStartOfLine = TRUE;
	int cchSection = lstrlen(pszSection) + 1;
	for (LPTSTR pch = m_pszFileData; (ch = *pch) != '\0'; pch++)
	{
		if (ch == '\r' || ch == '\n')
		{
			bStartOfLine = TRUE;
		}
		else if (bStartOfLine)
		{
			if (ch == '[')
			{
				LPTSTR pchCloseBracket = strchr(pch+1, ']');
				if ((int)(pchCloseBracket - pch) == cchSection)
				{
					CString str(pch+1, cchSection-1);
					if (str.CompareNoCase(pszSection) == 0)
					{
						pch = pchCloseBracket+1;
						if (*pch == '\r')
							pch++;
						if (*pch == '\n')
							pch++;
						m_iPos = (DWORD)(pch - m_pszFileData);
						return TRUE;
					}
				}
			}

			bStartOfLine = FALSE;
		}
	}

	return FALSE;
}

int CInfParser::GetProfileInt(LPCTSTR pszSection, LPCTSTR pszKey, int nDefault)
{
	DWORD iPos = m_iPos;

	if (GotoSection(pszSection))
	{
		CStringArray rgTokens;
		while (GetSectionLineTokens(rgTokens))
		{
			if (rgTokens.GetSize() >= 3 &&
				rgTokens.ElementAt(0).CompareNoCase(pszKey) == 0&&
				rgTokens.ElementAt(1).Compare("=") == 0)
			{
				nDefault = MyAtoi(rgTokens.ElementAt(2));
				break;
			}
		}
	}

	m_iPos = iPos;
	return nDefault;
}

BOOL CInfParser::GetFilesFromInstallSection(LPCTSTR pszSection, CDriverFileArray& rgAllFiles)
{
	CStringArray rgLineTokens;
	CStringArray rgCopyFilesSections;

	if (!GotoSection(pszSection))
		return FALSE;

	while (GetSectionLineTokens(rgLineTokens))
	{
		if (rgLineTokens.GetSize() >= 3 &&
			rgLineTokens.ElementAt(0).CompareNoCase("CopyFiles") == 0 &&
			rgLineTokens.ElementAt(1).Compare("=") == 0)
		{

			AddCommaSeparatedValues(rgLineTokens, rgCopyFilesSections, FALSE);

			// REVIEW: There can be AddReg= lines here.  Do we need to 
			// check the referenced sections for more CopyFiles= lines?
		}
	}

	GetFilesFromCopyFilesSections(rgCopyFilesSections, rgAllFiles);
	return TRUE;
}

// Looks in [DestinationDirs] for pszSectionName, fills pbDirNumber and pszSubDir with
// the matching target directory and (optional) subdirectory.
BOOL CInfParser::GetDestinationDir(LPCTSTR pszSectionName, BYTE* pbDirNumber, LPTSTR pszSubDir, UINT cchSubDir)
{
	DWORD iSavedPos = m_iPos;
	BOOL bSuccess = FALSE;

	*pbDirNumber = 0;
	pszSubDir[0] = '\0';

	if (GotoSection("DestinationDirs"))
	{
		CStringArray rgTokens;
		while (GetSectionLineTokens(rgTokens))
		{
			if (rgTokens.GetSize() >= 3 &&
				rgTokens.ElementAt(0).CompareNoCase(pszSectionName) == 0 &&
				rgTokens.ElementAt(1).Compare("=") == 0)
			{
				*pbDirNumber = (BYTE)MyAtoi(rgTokens.ElementAt(2));

				if (rgTokens.GetSize() >= 5 && rgTokens.ElementAt(3).Compare(",") == 0)
				{
					lstrcpyn(pszSubDir, rgTokens.ElementAt(4), cchSubDir);
				}

				bSuccess = TRUE;
				break;
			}
		}
	}

	m_iPos = iSavedPos;
	return bSuccess;
}

void CInfParser::GetFilesFromCopyFilesSections(const CStringArray& rgCopyFiles, CDriverFileArray& rgAllFiles)
{
	CStringArray rgLineTokens;

	for (int iSection = 0; iSection < rgCopyFiles.GetSize(); iSection++)
	{
		TCHAR szTargetSubDir[MAX_PATH];
		BYTE nTargetDir;
		GetDestinationDir(rgCopyFiles[iSection], &nTargetDir, szTargetSubDir, _countof(szTargetSubDir));

//		BYTE nTargetDir = (BYTE)GetProfileInt("DestinationDirs", rgCopyFiles[iSection], 0);

#ifdef _DEBUG
		if (nTargetDir == 0)
			TRACE("Warning: CopyFiles section [%s] has no destination directory.\r\n", rgCopyFiles[iSection]);
#endif

		if (!GotoSection(rgCopyFiles[iSection]))
			continue;

		// Get the first item from each line
		while (GetSectionLineTokens(rgLineTokens))
		{
			if (rgLineTokens.GetSize() == 1 ||
				(rgLineTokens.GetSize() >= 2 &&
				 (rgLineTokens.ElementAt(1).Compare(",") == 0 ||
				  rgLineTokens.ElementAt(1).Compare(";") == 0)))
			{
				CString& strFileName = rgLineTokens.ElementAt(0);

				// Don't install this INF file
				// REVIEW: might want to allow this based on a flag or something
				if (0 != lstrcmpi(FindFileTitle(strFileName), FindFileTitle(m_strFileName)))
				{
					UINT cbFileInfo = sizeof(DRIVER_FILE_INFO) + strFileName.GetLength() + lstrlen(szTargetSubDir) + 1;
					DRIVER_FILE_INFO* pFileInfo = (DRIVER_FILE_INFO*)malloc(cbFileInfo);
					pFileInfo->nTargetDir = nTargetDir;
					lstrcpy(pFileInfo->szFileTitle, strFileName);
					lstrcpy(pFileInfo->szFileTitle + strFileName.GetLength() + 1, szTargetSubDir);
					rgAllFiles.Add(pFileInfo);
				}
			}
		}
	}

	// TODO: Remove duplicate files (maybe here, maybe not)
}

int CInfParser::GetNextSourceFile(LPTSTR pszBuf, BYTE* pDiskNumber)
{
	LPTSTR pch = m_pszFileData + m_iPos;
	int cch = 0;
	BYTE bDiskNumber = 0;

	for (;;)
	{
		TCHAR ch;

		while ((ch = *pch) == '\r' || ch == '\n')
			pch++;

		if (ch == '\0' || ch == '[' || cch != 0)
			break;

		if (ch != ';')
		{
			LPTSTR pchStart = pch;
			while ((UCHAR)(ch = *pch) > 32 && ch != '=')
				pch++;
			cch = (int)(pch - pchStart);
			lstrcpyn(pszBuf, pchStart, cch+1);

			// skip whitespace while avoiding '\0'
			while ((UCHAR)(*pch-1) < 32)
				pch++;

			if (*pch == '=')
			{
				pch++;

				// skip whitespace while avoiding '\0'
				while ((UCHAR)(*pch-1) < 32)
					pch++;

				bDiskNumber = (BYTE)MyAtoi(pch);

#if 1 // ignore files with disk number of 0
				if (bDiskNumber == 0)
					cch = 0;
#endif
			}
		}

		// skip text up to newline
		while ((ch = *pch) != '\0' && ch != '\r' && ch != '\n')
			pch++;
	}

	*pDiskNumber = bDiskNumber;

	m_iPos = (DWORD)(pch - m_pszFileData);
	return cch;
}

/*
int CInfParser::ReadSourceFilesSection(INF_LAYOUT_FILE* prgFiles, int cFiles)
{
	WORD wOffset = (WORD)(cFiles * sizeof(INF_LAYOUT_FILE));
	LPTSTR pchDest = (LPTSTR)((LPBYTE)m_prgFiles + wOffset);
	INF_LAYOUT_FILE* pFile = prgFiles;
	INF_LAYOUT_FILE* pFileEnd = pFile + cFiles;

	while (pFile < pFileEnd)
	{
		pFile->wNameOffset = (WORD)((LPBYTE)pchDest - (LPBYTE)prgFiles);

		int cch = parser.GetNextSourceFile(pchDest, &pFile->iDisk);
		wOffset += cch+1;
		pchDest += cch+1;
		pFile++;
	}

	LPTSTR pch = m_pszFileData + m_iPos;
	int cch = 0;
	*pDiskNumber = 0;

	for (;;)
	{
		TCHAR ch;

		while ((ch = *pch) == '\r' || ch == '\n')
			pch++;

		if (ch == '\0' || ch == '[' || cch != 0)
			break;

		if (ch != ';')
		{
			LPTSTR pchStart = pch;
			while ((UCHAR)(ch = *pch) > 32 && ch != '=')
				pch++;
			cch = (int)(pch - pchStart);
			lstrcpyn(pszBuf, pchStart, cch+1);

			// skip whitespace while avoiding '\0'
			while ((UCHAR)(*pch-1) < 32)
				pch++;

			if (*pch == '=')
			{
				pch++;

				// skip whitespace while avoiding '\0'
				while ((UCHAR)(*pch-1) < 32)
					pch++;

				*pDiskNumber = (BYTE)atoi(pch);
			}
		}

		// skip text up to newline
		while ((ch = *pch) != '\0' && ch != '\r' && ch != '\n')
			pch++;
	}

	m_iPos = (DWORD)(pch - m_pszFileData);
	return cch;
}
*/

void CInfParser::ScanSourceFileList(int* pcFiles, int* pcchAllFileNames)
{
	int cFiles = 0;
	int cchAllFileNames = 0;

	LPTSTR pch = m_pszFileData + m_iPos;
	for (;;)
	{
		TCHAR ch;

		while ((ch = *pch) == '\r' || ch == '\n')
			pch++;

		if (ch == '\0' || ch == '[')
			break;

		if (ch != ';')
		{
			cFiles += 1;
			LPTSTR pchStart = pch;
			while ((UCHAR)(ch = *pch) >= 32 && ch != '=')
				pch++;
			cchAllFileNames += (int)(pch - pchStart);
		}

		// skip text up to newline
		while ((ch = *pch) != '\0' && ch != '\r' && ch != '\n')
			pch++;
	}

	*pcFiles = cFiles;
	*pcchAllFileNames = cchAllFileNames;
}

//////////////////////////////////////////////////////////////////////////////
// CInfLayoutFiles

#define INF_LAYOUT_FILE_PADDING 40 // extra bytes at end of string data

CInfLayoutFiles::CInfLayoutFiles()
{
	m_prgFiles = NULL;
	m_pStringData = NULL;
	m_cFiles = 0;
	m_cbStringData = 0;

#ifdef _DEBUG
	m_bSorted = FALSE;
#endif
}

CInfLayoutFiles::~CInfLayoutFiles()
{
	free(m_prgFiles);
	free(m_pStringData);

	for (int i = m_rgSourceDisks.GetSize()-1; i >= 0; i--)
	{
		delete m_rgSourceDisks[i];
	}
}

LPTSTR CInfLayoutFiles::s_pStringData;

int __cdecl CInfLayoutFiles::CompareInfLayoutFiles(const void* pEl1, const void* pEl2)
{
	LPCTSTR psz1 = s_pStringData + ((INF_LAYOUT_FILE*)pEl1)->dwNameOffset;
	LPCTSTR psz2 = s_pStringData + ((INF_LAYOUT_FILE*)pEl2)->dwNameOffset;
	return lstrcmpi(psz1, psz2);
}

BOOL CInfLayoutFiles::Add(CInfParser& parser, BOOL bLayoutFile)
{
	// Check if we've already added this layout file
	if (-1 != m_rgLayoutFileNames.Find(parser.m_strFileName))
		return TRUE;

	BYTE iLayoutFile = (BYTE)m_rgLayoutFileNames.GetSize();
	m_rgLayoutFileNames.Add(parser.m_strFileName, 0);

	if (parser.GotoSection("SourceDisksFiles"))
	{
		DWORD dwTicks1 = GetTickCount();

		int cFiles;
		int cchAllFileNames;
		parser.ScanSourceFileList(&cFiles, &cchAllFileNames);

		DWORD dwTicks2 = GetTickCount();

		DWORD dwOffset = (DWORD)m_cbStringData;
		int iFile = m_cFiles;

		m_cFiles += cFiles;
		m_cbStringData += cchAllFileNames + cFiles;
		m_prgFiles = (INF_LAYOUT_FILE*)realloc(m_prgFiles, m_cFiles * sizeof(INF_LAYOUT_FILE));
		m_pStringData = (LPTSTR)realloc(m_pStringData, m_cbStringData + INF_LAYOUT_FILE_PADDING);

		LPTSTR pchDest = m_pStringData + dwOffset;
		INF_LAYOUT_FILE* pFile = m_prgFiles + iFile;

		for ( ; iFile < m_cFiles; iFile++)
		{
			pFile->dwNameOffset = dwOffset;
			pFile->iLayout = iLayoutFile;
			int cch = parser.GetNextSourceFile(pchDest, &pFile->iDisk);
			dwOffset += cch+1;
			pchDest += cch+1;
			pFile++;
		}

		DWORD dwTicks3 = GetTickCount();

		CString str;
		str.Format("LoadLayout(%s) timings: %d ms, %d ms.  Total time: %d ms", 
			parser.m_strFileName, dwTicks2-dwTicks1, dwTicks3-dwTicks2, dwTicks3-dwTicks1);
		TRACE("%s\r\n", str);
	//	AfxMessageBox(str);

	//	for (int i = 0; i < cFiles; i++)
	//	{
	//		INF_LAYOUT_FILE* pFile = &m_prgFiles[i];
	//		TRACE("File %d: %s=%d\r\n", i, m_pStringData + pFile->dwNameOffset), pFile->iDisk);
	//	}

		#ifdef _DEBUG
			m_bSorted = FALSE;
		#endif
	}

	if (parser.GotoSection("SourceDisksNames"))
	{
		CStringArray rgTokens;
		while (parser.GetSectionLineTokens(rgTokens))
		{
			if (rgTokens.GetSize() >= 3)
			{
				BYTE iDiskNumber = (BYTE)MyAtoi(rgTokens.ElementAt(0));
				if (iDiskNumber != 0)
				{
					SOURCE_DISK_INFO* pDiskInfo = new SOURCE_DISK_INFO;
					pDiskInfo->wDiskID = MAKE_DISK_ID(iDiskNumber, iLayoutFile);

					// Get disk description, pull off quotes
					CString& strDesc = rgTokens.ElementAt(2);
					if (strDesc[0] == '\"')
						pDiskInfo->strDescription = strDesc.Mid(1, strDesc.GetLength()-2);
					else
						pDiskInfo->strDescription = strDesc;

					// If this is Layout*.inf, Get CAB filename, pull off quotes
					if (bLayoutFile && rgTokens.GetSize() >= 5)
					{
						CString& strCab = rgTokens.ElementAt(4);
						if (strCab[0] == '\"')
							pDiskInfo->strCabFile = strCab.Mid(1, strCab.GetLength()-2);
						else
							pDiskInfo->strCabFile = strCab;
					}

					m_rgSourceDisks.Add(pDiskInfo);
				}
			}
		}
	}

	//
	// Now add any referenced layout files
	//

	if (parser.GotoSection("version"))
	{
		CStringArray rgLayoutFiles;
		CStringArray rgLineTokens;

		while (parser.GetSectionLineTokens(rgLineTokens))
		{
			if (rgLineTokens.GetSize() >= 3 &&
				rgLineTokens.ElementAt(0).CompareNoCase("LayoutFile") == 0 &&
				rgLineTokens.ElementAt(1).Compare("=") == 0)
			{
				AddCommaSeparatedValues(rgLineTokens, rgLayoutFiles, FALSE);
				break;
			}
		}

		for (int i = 0; i < rgLayoutFiles.GetSize(); i++)
		{
			Add(rgLayoutFiles.ElementAt(i), TRUE);
		}
	}

	return TRUE;
}

BOOL CInfLayoutFiles::Add(LPCTSTR pszInfFile, BOOL bLayoutFile)
{
	CInfParser parser;
	if (!parser.LoadInfFile(pszInfFile))
		return FALSE;
	return Add(parser, bLayoutFile);
}

void CInfLayoutFiles::Sort()
{
	s_pStringData = m_pStringData;
	qsort(m_prgFiles, m_cFiles, sizeof(INF_LAYOUT_FILE), CompareInfLayoutFiles);

#ifdef _DEBUG
	m_bSorted = TRUE;
#endif
}

SOURCE_DISK_INFO* CInfLayoutFiles::FindDriverFileSourceDisk(LPCTSTR pszDriverFileTitle)
{
	ASSERT(m_bSorted);

	// Build a dummy layout-file key to allow the standard binary search to work
	// (Note that we've left INF_LAYOUT_FILE_PADDING chars at the end of the string data)
	ASSERT(lstrlen(pszDriverFileTitle) + 1 < INF_LAYOUT_FILE_PADDING);
	INF_LAYOUT_FILE key;
	key.dwNameOffset = m_cbStringData;
	lstrcpy(m_pStringData + m_cbStringData, pszDriverFileTitle);

	s_pStringData = m_pStringData;
	INF_LAYOUT_FILE* pResult = (INF_LAYOUT_FILE*)bsearch(
			&key, m_prgFiles, m_cFiles, sizeof(INF_LAYOUT_FILE), CompareInfLayoutFiles);

	if (pResult == NULL)
	{
		ASSERT(FALSE);
		return NULL;
	}

	// REVIEW: Is it worth making this a binary search?
	WORD wDiskID = MAKE_DISK_ID(pResult->iDisk, pResult->iLayout);
	for (int iDisk = 0; iDisk < m_rgSourceDisks.GetSize(); iDisk++)
	{
		SOURCE_DISK_INFO* pDiskInfo = m_rgSourceDisks[iDisk];
		if (pDiskInfo->wDiskID == wDiskID)
			return pDiskInfo;
	}

	ASSERT(FALSE);
	return NULL;
}

#ifdef _DEBUG
void CInfLayoutFiles::Dump()
{
	TRACE("CInfLayoutFiles (0x%08x)\r\n", (int)this);
	for (int i = 0; i < m_cFiles; i++)
	{
		INF_LAYOUT_FILE* pFile = &m_prgFiles[i];
		TRACE("  File %d: %s, layout %d, disk %d\r\n", i, m_pStringData + pFile->dwNameOffset, (int)pFile->iLayout, (int)pFile->iDisk);
	}
}
#endif // _DEBUG


//////////////////////////////////////////////////////////////////////////////
// CInfFileList

CInfFileList::CInfFileList()
{
}

CInfFileList::~CInfFileList()
{
	for (int i = m_rgDriverFiles.GetSize()-1; i >= 0; i--)
	{
		free(m_rgDriverFiles[i]);
	}

	for (i = m_rgCabFiles.GetSize() - 1; i >= 0; i--)
	{
		free((LPTSTR)m_rgCabFiles.GetItemData(i));
	}
}

void CInfFileList::SetDriverSourceDir(LPCTSTR pszSourceDir)
{
	m_strDriverSourceDir = pszSourceDir;
}

BOOL CInfFileList::AddBaseFiles(LPCTSTR pszInfFile)
{
	CInfParser parser;
	if (!parser.LoadInfFile(pszInfFile))
		return FALSE;

	if (!parser.GotoSection("BaseWinOptions"))
		return FALSE;

	CStringArray rgSections;
	CStringArray rgLineTokens;
	while (parser.GetSectionLineTokens(rgLineTokens))
	{
		if (rgLineTokens.GetSize() >= 1)
			rgSections.Add(rgLineTokens.ElementAt(0));
	}

	int cSections = rgSections.GetSize();
	if (cSections == 0)
		return FALSE;

	// Walk through each major section, grabbing section names from CopyFiles= lines
	CStringArray rgCopyFiles;
	for (int iSection = 0; iSection < cSections; iSection++)
	{
		if (!parser.GotoSection(rgSections[iSection]))
			continue;

		// Look for "CopyFiles="
		while (parser.GetSectionLineTokens(rgLineTokens))
		{
			if (rgLineTokens.GetSize() >= 3 && 
				rgLineTokens.ElementAt(0).CompareNoCase("CopyFiles") == 0 &&
				rgLineTokens.ElementAt(1).Compare("=") == 0)
			{
				AddCommaSeparatedValues(rgLineTokens, rgCopyFiles, TRUE);
			}
		}
	}

	// Walk through each CopyFiles section, grabbing the names of files to copy
	parser.GetFilesFromCopyFilesSections(rgCopyFiles, m_rgDriverFiles);

	m_rgLayoutFiles.Add(parser);

//	for (int i = 0; i < rgAllFiles.GetSize(); i++)
//	{
//		TRACE("File %d: %s\r\n", i, rgAllFiles[i]);
//	}

	return TRUE;
}

BOOL CInfFileList::AddDeviceFiles(LPCTSTR pszInfFile, LPCTSTR pszDeviceID)
{
	CInfParser parser;
	if (!parser.LoadInfFile(pszInfFile))
		return FALSE;

	if (!GetDeviceCopyFiles(parser, pszDeviceID, m_rgDriverFiles))
		return FALSE;

	// Build a list of all layout files, including current file
	m_rgLayoutFiles.Add(parser);

	return TRUE;
}

// Retrieves one of the standard setupx destination directories.
// Always appends a backslash to the name.
// Returns number of characters copied.
//
//    See setupx.h for a list of valid LDID_ values.
//
int GetStandardTargetPath(int iDirNumber, LPCTSTR pszTargetSubDir, LPTSTR pszBuf)
{
	int cch = GetWindowsDirectory(pszBuf, MAX_PATH);
	if (pszBuf[cch-1] != '\\')
		pszBuf[cch++] = '\\';

	switch (iDirNumber)
	{
	case 10: // LDID_WIN
		break;

	case 11: // LDID_SYS
		cch = GetSystemDirectory(pszBuf, MAX_PATH);
		if (pszBuf[cch-1] != '\\')
			pszBuf[cch++] = '\\';
		break;

	case 17: // LDID_INF
		lstrcpy(pszBuf + cch, "INF\\");
		cch += 4;
		break;

	case 18: // LDID_HELP
		lstrcpy(pszBuf + cch, "HELP\\");
		cch += 5;
		break;

	case 25: // LDID_SHARED (windows dir)
		break;

	case 26: // LDID_WINBOOT (windows dir)
		break;

	default:
		ASSERT(FALSE);
		cch = 0;
		break;
	}

	if (pszTargetSubDir != NULL && *pszTargetSubDir != '\0')
	{
		lstrcpy(pszBuf + cch, pszTargetSubDir);
		cch += lstrlen(pszTargetSubDir);
		if (pszBuf[cch-1] != '\\')
			pszBuf[cch++] = '\\';
	}

	pszBuf[cch] = '\0';
	return cch;
}


int GetDriverTargetPath(const DRIVER_FILE_INFO* pFileInfo, LPTSTR pszBuf)
{
	LPCTSTR pszTargetSubDir = pFileInfo->szFileTitle + lstrlen(pFileInfo->szFileTitle) + 1;
	return GetStandardTargetPath(pFileInfo->nTargetDir, pszTargetSubDir, pszBuf);
}


// Returns number of CAB files (from Windows CD) that need to be copied.
// Note that other files may still need to be copied from the driver source directory.
int CInfFileList::BuildSourceFileList()
{
	TCHAR szPath[MAX_PATH];

	CSortedStringArray& rgCabFiles = m_rgCabFiles;
	CSortedStringArray& rgSourceFiles = m_rgSourceFiles;

	m_rgLayoutFiles.Sort();

#ifdef _DEBUG
//	m_rgLayoutFiles.Dump();
#endif

	for (int iDriverFile = 0; iDriverFile < m_rgDriverFiles.GetSize(); iDriverFile++)
	{
		DRIVER_FILE_INFO* pDriverFileInfo = m_rgDriverFiles[iDriverFile];

		// Check if file is already installed
//		GetStandardTargetPath(pDriverFileInfo->nTargetDir, szPath);
//		MakePath(szPath, szPath, pDriverFileInfo->szFileTitle);
//		if (DoesFileExist(szPath))
//			continue; // skip this file

		if (!m_strDriverSourceDir.IsEmpty())
		{
			// Check if file exists in source directory
			MakePath(szPath, m_strDriverSourceDir, pDriverFileInfo->szFileTitle);
			if (DoesFileExist(szPath))
			{
				if (-1 == rgSourceFiles.Find(pDriverFileInfo->szFileTitle))
				{
					rgSourceFiles.Add(pDriverFileInfo->szFileTitle, 0);
				}
				continue;
			}
		}

		SOURCE_DISK_INFO* pSourceDiskInfo = m_rgLayoutFiles.FindDriverFileSourceDisk(pDriverFileInfo->szFileTitle);
		if (pSourceDiskInfo != NULL && !pSourceDiskInfo->strCabFile.IsEmpty())
		{
			if (-1 == rgCabFiles.Find(pSourceDiskInfo->strCabFile))
			{
				LPTSTR pszDiskName = lstrdup(pSourceDiskInfo->strDescription);
				rgCabFiles.Add(pSourceDiskInfo->strCabFile, (DWORD)pszDiskName);
			}
			continue;
		}
	}

	for (int i = 0; i < rgCabFiles.GetSize(); i++)
	{
		TRACE("%s\r\n", rgCabFiles[i]);
	}

	return rgCabFiles.GetSize();
}

#if NOT_FINISHED
BOOL CInfFileList::CheckWindowsCD(LPCTSTR pszDirectory)
{
	if (m_rgCabFiles.GetSize() == 0)
		return TRUE; // no files to copy

	UINT uPrevErrorMode = SetErrorMode(SEM_NOOPENFILEERRORBOX | SEM_FAILCRITICALERRORS);

	// Check for existence of one of the cabs on the CD
	// REVIEW: if it's a fixed disk, should check for all files
	// REVIEW: if it's a network share, this could take a long time

	TCHAR szPath[MAX_PATH];
	BOOL bResult = FALSE;
	MakePath(szPath, pszDirectory, ".");
	if (DoesFileExist(szPath))
	{
		MakePath(szPath, pszDirectory, m_rgCabFiles[0]);
		bResult = DoesFileExist(szPath);

		if (!bResult)
		{
			// Search one level of subdirectories starting with "W"
			WIN32_FIND_DATA Find;
			HANDLE hFind;
			MakePath(szPath, pszDirectory, "W*.*");
			if (INVALID_HANDLE_VALUE != (hFind = FindFirstFile(szPath, &Find)))
			{
				do
				{
					MakePath(szPath, pszDirectory, Find.cFileName);
					MakePath(szPath, szPath, m_rgCabFiles[0]);
					if (DoesFileExist(szPath))
					{
						bResult = TRUE;
						break;
					}
				}
				while (FindNextFile(hFind, &Find));
				FindClose(hFind);
			}
		}
	}

	SetErrorMode(uPrevErrorMode);
	return bResult;
}

BOOL CInfFileList::FindWindowsCD(HWND hwndParent)
{
	TCHAR szPath[MAX_PATH];

	// check the version of the Windows source path that we've saved
	if (theApp.GetProfileString(c_szRegVal_PrevSourcePath, szPath, _countof(szPath)))
	{
		if (CheckWindowsCD(szPath))
		{
//			goto success;
		}
	}
	else
	{
		// Check the current Windows source path
		CRegistry reg;
		if (reg.OpenKey(HKEY_LOCAL_MACHINE, c_szSetupKey) &&
			reg.QueryStringValue(c_szRegVal_SourcePath, szPath, _countof(szPath)))
		{
			if (CheckWindowsCD(szPath))
			{
				goto success;
			}
		}
	}

	if (!PromptWindowsCD(hwndParent, szPath, szPath))
		return FALSE;

success:
	m_strWindowsCD = szPath;
	return TRUE;
}

BOOL CInfFileList::PromptWindowsCD(HWND hwndParent, LPCTSTR pszInitialDir, LPTSTR pszResultDir)
{
	LPCTSTR pszDiskName = (LPCTSTR)m_rgCabFiles.GetItemData(0);
	CWinPathDlg dlg(pszInitialDir, pszDiskName, CWnd::FromHandle(hwndParent));

	// Loop until user types a path to a valid CD, or clicks Cancel
	for (;;)
	{
		if (IDOK != dlg.DoModal())
			return FALSE;

		if (CheckWindowsCD(dlg.m_strPath))
		{
			lstrcpy(pszResultDir, dlg.m_strPath);
			return TRUE;
		}
	}
}

BOOL CInfFileList::CopySourceFiles(HWND hwndParent, LPCTSTR pszDestDir, PROGRESS_CALLBACK pfnProgress, LPVOID pvProgressParam)
{
	int cFiles = m_rgCabFiles.GetSize() + m_rgSourceFiles.GetSize();

	if (m_rgCabFiles.GetSize() > 0)
	{
	}

	return TRUE;
}
#endif

//////////////////////////////////////////////////////////////////////////////
// Modify system INF

class CInfUpdater : public CInfParser
{
public:
	CInfUpdater(LPCTSTR pszRequire = NULL, LPCTSTR pszExclude = NULL);
	~CInfUpdater();

	enum eUpdateType {
		update_NoVersionConflict	= 0x01,
		update_NoCopyFiles			= 0x02,
		update_RequireExclude		= 0x04,
	};

	BOOL IsModified();
	BOOL UpdateInfFile(LPCTSTR pszBackupLocation, UINT updateType);

	LPCTSTR m_pszRequire;
	LPCTSTR m_pszExclude;

protected:

	void WriteToCurPos();
	void Write(LPCTSTR pszString);
	void Write(const void* pvData, UINT cbData);
	BOOL OpenTempFile();
	BOOL CloseTempFile();
	BOOL RenameTempFile();

protected:
	CString	m_strTempFile;
	HANDLE	m_hTempFile;
	BOOL	m_bWriteSuccess;
	DWORD	m_iWritePos;
};

CInfUpdater::CInfUpdater(LPCTSTR pszRequire /*=NULL*/, LPCTSTR pszExclude /*=NULL*/)
{
	m_hTempFile = INVALID_HANDLE_VALUE;
	m_pszRequire = pszRequire;
	m_pszExclude = pszExclude;
}

CInfUpdater::~CInfUpdater()
{
	CloseTempFile();
	if (!m_strTempFile.IsEmpty())
	{
		DeleteFile(m_strTempFile);
	}
}

BOOL CInfUpdater::IsModified()
{
	ASSERT(m_pszFileData != NULL);
	return !memcmp(SZ_CHECK_MODIFIED_HEADER, m_pszFileData, _lengthof(SZ_CHECK_MODIFIED_HEADER));
}

BOOL CInfUpdater::UpdateInfFile(LPCTSTR pszBackupLocation, UINT updateType)
{
	ASSERT(m_pszFileData != NULL);
	if (m_pszFileData == NULL)
		return FALSE;

	Rewind();

	if (!OpenTempFile())
		return FALSE;

	Write(SZ_MODIFIED_INF_HEADER);
	Write(pszBackupLocation);
	Write(SZ_MODIFIED_INF_HEADER2);

	BOOL bInSection = FALSE;
	for (;;)
	{
		// Skip whitespace
		while (m_pszFileData[m_iPos] == ' ' || m_pszFileData[m_iPos] == '\t')
			m_iPos++;

		// Note: we're always at the beginning of a line here.
		TCHAR ch = m_pszFileData[m_iPos];

		if (ch == '\0')
			break;

		if (updateType & update_NoVersionConflict)
		{
			if (ch != '[' && ch != ';' && (UINT)ch > ' ')
			{
				// Look for lines w/ just a filename, and append ",,,32" to them

				LPTSTR pszLine = m_pszFileData + m_iPos;
				LPTSTR pchEnd = pszLine;
				while (*pchEnd != '\0' && *pchEnd != '\r' && *pchEnd != '\n' &&
					   *pchEnd != ';' && *pchEnd != '=' && *pchEnd != ',')
				{
					pchEnd++;
				}

				// don't change lines that already have ,,,16 or something
				// also don't change lines like CatalogFile=nettrans.cat
				if (*pchEnd != ',' && *pchEnd != '=') 
				{
					// Backup over whitespace
					while (*(pchEnd-1) == ' ' || *(pchEnd-1) == '\t')
						pchEnd--;

					// Is it a filename? Note that some filenames will be missed here, but
					// we really only care about .dll, .386, .vxd, and a few others
					CString str(pszLine, (int)(pchEnd - pszLine));
					if (lstrlen(FindExtension(str)) == 3)
					{
						m_iPos = (DWORD)(pchEnd - m_pszFileData);
						WriteToCurPos();
						Write(",,,32");
					}
				}
			}
		}
		if (updateType & update_NoCopyFiles)
		{
			if (0 == memcmp(m_pszFileData + m_iPos, "CopyFiles", _lengthof("CopyFiles")))
			{
				// Comment out the reference to the CopyFiles section
				WriteToCurPos();
				Write(";hc ");
			}
		}
		if (updateType & update_RequireExclude)
		{
			ASSERT(m_pszRequire != NULL);
			ASSERT(m_pszExclude != NULL);

			const TCHAR c_szRequireAll[] = _T("HKR,Ndi\\Compatibility,RequireAll,,\"");
			const TCHAR c_szExcludeAll[] = _T("HKR,Ndi\\Compatibility,ExcludeAll,,\"");
			LPCTSTR pszInsert = NULL;

			if (0 == memcmp(m_pszFileData + m_iPos, c_szRequireAll, _lengthof(c_szRequireAll)))
				pszInsert = m_pszRequire;
			else if (0 == memcmp(m_pszFileData + m_iPos, c_szExcludeAll, _lengthof(c_szExcludeAll)))
				pszInsert = m_pszExclude;

			if (pszInsert != NULL)
			{
				// Insert the appropriate string between the double-quotes
				ASSERT(_lengthof(c_szRequireAll) == _lengthof(c_szExcludeAll));
				m_iPos += _lengthof(c_szRequireAll);
				WriteToCurPos();
				Write(pszInsert);

				// Skip to closing quote
				while (m_pszFileData[m_iPos] != '\"' && m_pszFileData[m_iPos] != '\0')
					m_iPos += 1;
				m_iWritePos = m_iPos;
			}
		}

		GotoNextLine();
	}

	WriteToCurPos();

	if (!CloseTempFile())
		return FALSE;

	if (!RenameTempFile())
		return FALSE;

	return TRUE;
}

void CInfUpdater::WriteToCurPos()
{
	ASSERT(m_iPos >= m_iWritePos);
	Write(m_pszFileData + m_iWritePos, m_iPos - m_iWritePos);
	m_iWritePos = m_iPos;
}

void CInfUpdater::Write(LPCTSTR pszString)
{
	Write(pszString, lstrlen(pszString));
}

void CInfUpdater::Write(const void* pvData, UINT cbData)
{
	DWORD cbWritten;
	if (!WriteFile(m_hTempFile, pvData, cbData, &cbWritten, NULL) || cbData != cbWritten)
		m_bWriteSuccess = FALSE;
}

BOOL CInfUpdater::OpenTempFile()
{
	TCHAR szDirectory[MAX_PATH];
	GetFullInfPath(m_strFileName, szDirectory, _countof(szDirectory));
	*(FindFileTitle(szDirectory)) = '\0';

	LPTSTR pszBuf = m_strTempFile.GetBuffer(MAX_PATH);
	GetTempFileName(szDirectory, "inf", 0, pszBuf);
	m_strTempFile.ReleaseBuffer();

	ASSERT(m_hTempFile == INVALID_HANDLE_VALUE);
	m_hTempFile = CreateFile(m_strTempFile, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
	if (m_hTempFile == INVALID_HANDLE_VALUE)
		return FALSE;

	m_iWritePos = 0;
	m_bWriteSuccess = TRUE;

	return TRUE;
}

BOOL CInfUpdater::CloseTempFile()
{
	if (m_hTempFile == INVALID_HANDLE_VALUE)
		return FALSE;

	BOOL bResult = CloseHandle(m_hTempFile);
	m_hTempFile = INVALID_HANDLE_VALUE;
	if (m_bWriteSuccess)
		m_bWriteSuccess = bResult;
	return m_bWriteSuccess;
}

BOOL CInfUpdater::RenameTempFile()
{
	TCHAR szInfPath[MAX_PATH];
	GetFullInfPath(m_strFileName, szInfPath, _countof(szInfPath));
	if (!DeleteFile(szInfPath))
		return FALSE;
	if (!MoveFile(m_strTempFile, szInfPath))
		return FALSE;

	return TRUE;
}

// Modifies the given INF file to avoid the Version Conflict dialog.
// Backs up original version in same directory, e.g. "Net (HomeClick backup).inf"
BOOL ModifyInf_Helper(LPCTSTR pszInfFile, UINT updateType, LPCTSTR pszRequire = NULL, LPCTSTR pszExclude = NULL)
{
	CInfUpdater infUpdate(pszRequire, pszExclude);
	if (!infUpdate.LoadInfFile(pszInfFile))
		return FALSE;

	// Already updated?
	if (infUpdate.IsModified())
		return FALSE;	// already modified, don't modify again

	TCHAR szInfPath[MAX_PATH];
	GetFullInfPath(pszInfFile, szInfPath, _countof(szInfPath));

	TCHAR szBackup[MAX_PATH];
	lstrcpy(szBackup, szInfPath);
	lstrcpy(FindExtension(szBackup)-1, SZ_INF_BACKUP_SUFFIX);

	FILETIME ftSrcCreated;
	FILETIME ftSrcModified;
	HANDLE hFile = CreateFile(szInfPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	GetFileTime(hFile, &ftSrcCreated, NULL, &ftSrcModified);
	CloseHandle(hFile);

	if (!CopyFile(szInfPath, szBackup, FALSE))
		return FALSE;

	BOOL bResult = infUpdate.UpdateInfFile(szBackup, updateType);

	hFile = CreateFile(szInfPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	DWORD dwFileSize = GetFileSize(hFile, NULL);
	SetFileTime(hFile, &ftSrcCreated, NULL, &ftSrcModified);
	CloseHandle(hFile);

	return bResult;
}

BOOL ModifyInf_NoVersionConflict(LPCTSTR pszInfFile)
{
	return ModifyInf_Helper(pszInfFile, CInfUpdater::update_NoVersionConflict);
}

BOOL ModifyInf_NoCopyFiles(LPCTSTR pszInfFile)
{
	return ModifyInf_Helper(pszInfFile, CInfUpdater::update_NoCopyFiles);
}

BOOL ModifyInf_RequireExclude(LPCTSTR pszInfFile, LPCTSTR pszRequire, LPCTSTR pszExclude)
{
	return ModifyInf_Helper(pszInfFile, CInfUpdater::update_RequireExclude, pszRequire, pszExclude);
}

BOOL ModifyInf_NoCopyAndRequireExclude(LPCTSTR pszInfFile, LPCTSTR pszRequire, LPCTSTR pszExclude)
{
	return ModifyInf_Helper(pszInfFile, 
							CInfUpdater::update_NoCopyFiles | CInfUpdater::update_RequireExclude, 
							pszRequire, pszExclude);
}

BOOL RestoreInfBackup(LPCTSTR pszInfFile)
{
	TCHAR szInfPath[MAX_PATH];
	GetFullInfPath(pszInfFile, szInfPath, _countof(szInfPath));

	TCHAR szBackup[MAX_PATH];
	lstrcpy(szBackup, szInfPath);
	lstrcpy(FindExtension(szBackup)-1, SZ_INF_BACKUP_SUFFIX);

	/*
	FILETIME ftSrcCreated;
	FILETIME ftSrcModified;
	HANDLE hFile = CreateFile(szInfPath, GENERIC_READ, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	GetFileTime(hFile, &ftSrcCreated, NULL, &ftSrcModified);
	CloseHandle(hFile);
	*/

	if (!DoesFileExist(szBackup))
		return FALSE;

	if (!DeleteFile(szInfPath))
		return FALSE;

	if (!MoveFile(szBackup, szInfPath))
		return FALSE;

	/*
	hFile = CreateFile(szInfPath, GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
	GetFileTime(hFile, &ftSrcCreated, NULL, &ftSrcModified);
	CloseHandle(hFile);
	*/

	return TRUE;
}

BOOL CheckInfSectionInstallation(LPCTSTR pszInfFile, LPCTSTR pszInfSection)
{
	CInfParser parser;
	if (!parser.LoadInfFile(pszInfFile))
	{
		ASSERT(FALSE);
		return TRUE; // all known files are present even though it's an error
	}

	CDriverFileArray rgFiles;
	if (!parser.GetFilesFromInstallSection(pszInfSection, rgFiles))
	{
		ASSERT(FALSE);
		return TRUE; // all known files are present even though it's an error
	}

	TCHAR szPath[MAX_PATH];
	int cFiles = rgFiles.GetSize();
	for (int iFile = 0; iFile < cFiles; iFile++)
	{
		int cch = GetDriverTargetPath(rgFiles[iFile], szPath);
		if (cch != 0)
		{
			lstrcpy(szPath + cch, rgFiles[iFile]->szFileTitle);
			if (!DoesFileExist(szPath))
				return FALSE;
		}
	}

	return TRUE;
}

BOOL InstallInfSection(LPCTSTR pszInfFile, LPCTSTR pszInfSection, BOOL bWait)
{
	// Make a modified copy of the INF
	BOOL bModifiedInf = ModifyInf_NoVersionConflict(pszInfFile);

	TCHAR szPath[MAX_PATH + 200];
	int cch = GetWindowsDirectory(szPath, _countof(szPath));
#ifdef UNICODE
	wnsprintf(szPath + cch, ARRAYSIZE(szPath) - cch, L"\\RunDll.exe setupx.dll,InstallHinfSection %s 0 %s", pszInfSection, pszInfFile);
#else
	wsprintf(szPath + cch, "\\RunDll.exe setupx.dll,InstallHinfSection %s 0 %s", pszInfSection, pszInfFile);
#endif
	STARTUPINFO si;
	ZeroMemory(&si, sizeof(si));
	si.cb = sizeof(si);
	PROCESS_INFORMATION pi;
	BOOL bResult = CreateProcess(NULL, szPath, NULL, NULL, FALSE, 0, NULL, NULL, &si, &pi);
	if (bResult)
	{
		if (bWait)
		{
			WaitForSingleObject(pi.hProcess, INFINITE);
		}
		CloseHandle(pi.hProcess);
		CloseHandle(pi.hThread);
	}

	if (bModifiedInf)
	{
		RestoreInfBackup(pszInfFile);
	}

	return bResult;
}