/***************************************************************************
 *
 * File Name: WPNPIN16.C
 *
 * Copyright 1997 Hewlett-Packard Company.  
 * All rights reserved.
 *
 * 11311 Chinden Blvd.
 * Boise, Idaho  83714
 *
 *   
 * Description: Source code for WPNPIN16.DLL
 *
 * Author:  Garth Schmeling
 *        
 *
 * Modification history:
 *
 * Date                Initials                Change description
 *
 * 10-10-97    GFS                             Initial checkin
 *
 *
 *
 ***************************************************************************/

#include "wpnpin16.h"

/*--------------- For Debug -------------------------*/
//#define GARTH_DEBUG 1


/*****************************************************************************\
* strFree
*
*   Free allocated string.
*
\*****************************************************************************/
VOID strFree(
    HANDLE hszStr,
    LPSTR  pszStr)
{
    if (hszStr && pszStr)
        GlobalUnlock(hszStr);

    if (hszStr)
        GlobalFree(hszStr);
}

/*****************************************************************************\
* strAlloc
*
*   Allocates a string from the heap.  This pointer must be freed with
*   a call to strFree().
*
\*****************************************************************************/
LPSTR strAlloc(
    LPHANDLE phSrc,
    LPCSTR   pszSrc)
{
    DWORD cbSize;
    LPSTR pszDst = NULL;


    *phSrc = NULL;
    cbSize = (pszSrc ? (lstrlen(pszSrc) + 1) : 0);


    if (cbSize && (*phSrc = GlobalAlloc(GPTR, cbSize))) {

        if (pszDst = (LPSTR)GlobalLock(*phSrc))
            lstrcpy(pszDst, pszSrc);
    }

    return pszDst;
}


/*****************************************************************************\
* strLoad
*
*   Get string from resource based upon the ID passed in.
*
\*****************************************************************************/
LPSTR strLoad(
    LPHANDLE phszStr,
    UINT     ids)
{
    char szStr[_MAX_RESBUF];


    if (LoadString(g_hInst, ids, szStr, sizeof(szStr)) == 0)
        szStr[0] = '\0';

    return strAlloc(phszStr, szStr);
}


/*****************************************************************************\
* InitStrings
*
*
\*****************************************************************************/
BOOL InitStrings(VOID)
{
    cszDefaultPrintProcessor = strLoad(&hszDefaultPrintProcessor, IDS_DEFAULT_PRINTPROCESSOR);
    cszMSDefaultDataType     = strLoad(&hszMSDefaultDataType    , IDS_DEFAULT_DATATYPE);
    cszDefaultColorPath      = strLoad(&hszDefaultColorPath     , IDS_COLOR_PATH);
    cszFileInUse             = strLoad(&hszFileInUse            , IDS_ERR_FILE_IN_USE);

    return (cszDefaultPrintProcessor &&
            cszMSDefaultDataType     &&
            cszDefaultColorPath      &&
            cszFileInUse);
}


/*****************************************************************************\
* FreeeStrings
*
*
\*****************************************************************************/
VOID FreeStrings(VOID)
{
    strFree(hszDefaultPrintProcessor, cszDefaultPrintProcessor);
    strFree(hszMSDefaultDataType    , cszMSDefaultDataType);
    strFree(hszDefaultColorPath     , cszDefaultColorPath);
    strFree(hszFileInUse            , cszFileInUse);
}


/*****************************************************************************\
* DllEntryPoint
*
*
\*****************************************************************************/
BOOL FAR PASCAL DllEntryPoint(
    DWORD dwReason,
	WORD  hInst,
	WORD  wDS,
	WORD  wHeapSize,
	DWORD dwReserved1,
	WORD  wReserved2)
{
    if (g_hInst == NULL) {

        g_hInst = (HINSTANCE)hInst;

        if (InitStrings() == FALSE)
            return FALSE;
    }

    return thk_ThunkConnect16(cszDll16, cszDll32, hInst, dwReason);
}


//-----------------------------------------------------------------------
// Function: lstrpbrk(lpSearch,lpTargets)
//
// Action: DBCS-aware version of strpbrk.
//
// Return: Pointer to the first character that in lpSearch that is also
//         in lpTargets. NULL if not found.
//
// Comment: Use nested loops to avoid allocating memory for DBCS stuff.
//-----------------------------------------------------------------------
LPSTR WINAPI lstrpbrk(LPSTR lpSearch,
					LPSTR lpTargets)
{
	LPSTR lpOneTarget;

	if (lpSearch AND lpTargets)
	{
		for (;
			 *lpSearch;
			 lpSearch=AnsiNext(lpSearch))
		{
			for (lpOneTarget=lpTargets;
				 *lpOneTarget;
				 lpOneTarget=AnsiNext(lpOneTarget))
			{
				if (*lpSearch==*lpOneTarget)
				{
					// First byte matches--see if we need to check
					// second byte
					if (IsDBCSLeadByte(*lpOneTarget))
					{
						if (*(lpSearch+1) == *(lpOneTarget+1))
							return lpSearch;
					}
					else
						return lpSearch;
				}
			}
		}
	}

	return NULL;
}



//-----------------------------------------------------------------------
// Function: lstrtok(lpSearch,lpTargets)
//
// Action: DBCS-aware version of strtok.
//
// Return: Pointer to next non-NULL token if found, NULL if not.
//-----------------------------------------------------------------------
LPSTR WINAPI lstrtok(LPSTR lpSearch,
					LPSTR lpTargets)
{
	static LPSTR lpLastSearch;
	LPSTR lpFound;
	LPSTR lpReturn=NULL;

	if (lpSearch)
		lpLastSearch=lpSearch;

	for (;
		 lpLastSearch AND *lpLastSearch;
		 lpLastSearch=AnsiNext(lpLastSearch))
	{
		// Skip leading white space
		while (' '==*lpLastSearch OR '\t'==*lpLastSearch)
			lpLastSearch++;

		if (lpFound=lstrpbrk(lpLastSearch,lpTargets))
		{
			if (lpFound==lpLastSearch)		 // Ignore NULL tokens
				continue;

			lpReturn=lpLastSearch;
			*lpFound='\0';
			lpLastSearch=lpFound+1;
			break;
		}
		else
		{
			lpReturn=lpLastSearch;
			lpLastSearch=NULL;
			break;
		}
	}

	return lpReturn;
}



//--------------------------------------------------------------------
// Function: FindCorrectSection(szInfFile,szManufacturer,szModel,szSection)
//
// Action: Find the install section in the inf file that corresponds 
//                 to the model name.  This may require several different approaches.
//                 Try the most likely first and then try others.  
//
// Side effect: Put the section name in szSection.
//
// Return: TRUE if the section was found, FALSE if not.
//         
//--------------------------------------------------------------------
BOOL WINAPI FindCorrectSection(
    LPSTR  szInfFile,
    LPSTR szManufacturer,
    LPSTR szModel,
    LPSTR szSection)
{
	HINF     hInf = 0;
	HINFLINE hInfLine = 0;
	int      i = 0;
	int      nCount = 0;
	int      nCopied = 0;
	int      nFields = 0;
	BOOL     bHaveManu = FALSE;
	char     lpszBuf[_MAX_LINE];
	char     lpszTemp[_MAX_LINE];
	char     lpszTemp2[_MAX_LINE];

	// Open the INF file.
	if (OK != IpOpen(szInfFile, &hInf))
	{
		return FALSE;
	}

	lstrcpy(lpszBuf, cszNull);
	lstrcpy(lpszTemp, cszNull);
	lstrcpy(lpszTemp2, cszNull);

	// Try # 1 to get the manufacturer's section name
	// Look for a section corresponding to the manufacturer's 
	// name.  Copy it to lpszBuf.  Open that section below.
	if (OK == IpFindFirstLine(hInf, szManufacturer, NULL, &hInfLine))
	{
		lstrcpy(lpszBuf, szManufacturer);
		bHaveManu = TRUE;

        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #1, Main [HP] Section"));

	} // Try # 1

	// Try # 2 to get the manufacturer's section name
	// Try the main [Manufacturer] section.
	// Cycle through each name expecting the name to be in the 
	// Strings section.
	if (!bHaveManu AND
		 (OK == IpFindFirstLine(hInf, "Manufacturer", NULL, &hInfLine)))
	{
		// Get the number of lines
		if (OK == IpGetLineCount(hInf, "Manufacturer", &nCount))
		{
			for (i = 0; i < nCount; i++)
			{
				lpszBuf[0] = '\0';
				lpszTemp[0] = '\0';     
				if (OK == IpGetStringField(hInf, hInfLine, 0, lpszTemp, _MAX_LINE, &nCopied))
				{
					GenFormStrWithoutPlaceHolders(lpszBuf,lpszTemp,hInf);

					// Garth: Use CompareString with the 
					// IGNORE_CASE, IGNORE_KANATYPE, and IGNORE_WIDTH flags
					if (lstrcmpi(szManufacturer, lpszBuf) == 0)
					{
						// We have found the manufacturer name, see if
						// there is a key on the right
						nFields = 0;
						if ( (OK == IpGetFieldCount(hInf, hInfLine, &nFields)) AND
							  (nFields > 0) )
						{
							if (OK == IpGetStringField(hInf, hInfLine, 1, lpszBuf, _MAX_LINE, &nCopied))
							{
								// There was a value on the right. It is stored in lpszBuf.
								// Just try to use it below as the name of the main section.

                                DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #2 Before NULL, lpszTemp = %s", lpszTemp));
                                DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Manual Install = %s", lpszBuf));

								lpszTemp[0] = '\0';
							}
						}
						else
						{
							// There was no value on the right, use the name from
							// lpszTemp
							lpszBuf[0] = '\0';
							lstrcpy(lpszBuf, lpszTemp);

                            DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #3 Manual"));
						}

						bHaveManu = TRUE;
						break;
					}
				}

				if (OK != IpFindNextLine(hInf, &hInfLine))
				{
					break;
				}
			}
		}

	} // Try # 2


	// Try # 3 to get the manufacturer's section name
	// Try the main [Manufacturer] section.
	// The printer manufacturer name does not match the 
	// driver manufacturer name.  (IE Canon and Epson with GCA)
	// Check to see if there is only one name in the manufacturer section.
	if (!bHaveManu AND
		 (OK == IpFindFirstLine(hInf, "Manufacturer", NULL, &hInfLine)))
	{
		// Get the number of lines
		if (OK == IpGetLineCount(hInf, "Manufacturer", &nCount))
		{
			if (nCount IS 1)
			{
				// Only one line in manufaturer section.
				// This has to be our guy.
				// See if there is a key on the right
				nFields = 0;
				lpszBuf[0] = '\0';
				IpGetFieldCount(hInf, hInfLine, &nFields);

				if (nFields > 0)
				{
					if (OK == IpGetStringField(hInf, hInfLine, 1, lpszBuf, _MAX_LINE, &nCopied))
					{
						// There was a value on the right. It is stored in lpszBuf.
						// Try to use it below as the name of the main section.
                        //
                        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Before NULL <%s>", lpszTemp));
                        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #4 One Manual <%s>", lpszBuf));

						lpszTemp[0] = '\0';
						bHaveManu = TRUE;
					}
				}
				else
				{
					// There was no value on the right.  Get the key on the left.
					if (OK == IpGetStringField(hInf, hInfLine, 0, lpszBuf, _MAX_LINE, &nCopied))
					{
						lpszTemp[0] = '\0';
						bHaveManu = TRUE;

                        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #6 One Manual"));
					}
				}
			}
		}

	} // Try # 3


	// Try # 4 to get the manufacturer's section name
	// Try a [PnP] section.
	if (!bHaveManu AND (OK == IpFindFirstLine(hInf, "PnP", NULL, &hInfLine)))
	{
		lpszBuf[0] = '\0';
		lstrcpy(lpszBuf, "PnP");
		bHaveManu = TRUE;

        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #6 [Pnp]"));

	} // Try # 4


	// Try # 6.  Get the install section associated with this
	// manufacturer and model.
	//
	// Look for a manufacturer's section with a profile key 
	// corresponding to the model name.  The first key on the RHS
	// should be the model section name.
    //
	if (bHaveManu AND (OK == IpFindFirstLine(hInf, lpszBuf, NULL, &hInfLine)))
	{
        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Found first line <%s>", lpszBuf));
        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Search for this model <%s>", szModel));

		lstrcpy(lpszTemp, cszNull);

		if (OK == IpGetProfileString(hInf,lpszBuf,szModel,lpszTemp,_MAX_LINE))
		{
            DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Profile string before lstrtok"));

			lstrtok(lpszTemp, cszComma);

			// quick check of model section name
            //
			if (OK == IpFindFirstLine(hInf, lpszTemp, NULL, &hInfLine))
			{
				if (
					(OK == IpGetProfileString(hInf,lpszTemp,"CopyFiles",lpszTemp2,_MAX_LINE))
					OR (OK == IpGetProfileString(hInf,lpszTemp,"DataSection",lpszTemp2,_MAX_LINE))
					OR (OK == IpGetProfileString(hInf,lpszTemp,"DriverFile",lpszTemp2,_MAX_LINE))
					OR (OK == IpGetProfileString(hInf,lpszTemp,"DataFile",lpszTemp2,_MAX_LINE)))
				{
					lstrcpyn(szSection, lpszTemp, _MAX_PATH_);
					IpClose(hInf);

                    DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #7 Found Correct Section"));

					return TRUE;
				}


				// We have a bad INF file:
				// We have the manufacturer name and the model name and an
				// install section, but it doesn't have any of the keys we need.
				// bail.
                //
				IpClose(hInf);

                DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #8 Bad INF file"));

				return FALSE;
			}
		}


		// The model name doesn't appear as a profile string. 
		// Perhaps a variable from the Strings sections appears in its place.
		// Try that.
		if ( (OK == IpGetLineCount(hInf, lpszBuf, &nCount)) AND 
			  (OK == IpFindFirstLine(hInf, lpszBuf, NULL, &hInfLine)) )
		{
			for (i = 0; i < nCount; i++)
			{
				lstrcpy(lpszTemp, cszNull);     

				if (OK == IpGetStringField(hInf, hInfLine, 0, lpszTemp, _MAX_LINE, &nCopied))
				{
					GenFormStrWithoutPlaceHolders(lpszTemp2,lpszTemp,hInf);

                    DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : lpszTemp2=%s, lpszTemp=%s", lpszTemp2, lpszTemp));

					if (lstrcmpi(szModel, lpszTemp2) == 0)
					{
						// The model name has a string var on the left
						// Get field 1.  This is the install section name.
						if (OK == IpGetStringField(hInf, hInfLine, 1, lpszTemp, _MAX_LINE, &nCopied))
						{
                            DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : Before lstrtok comma"));

							lstrtok(lpszTemp, cszComma);

							// quick check of model section name
                            //
							if (OK == IpFindFirstLine(hInf, lpszTemp, NULL, &hInfLine))
							{
								if ((OK == IpGetProfileString(hInf,lpszTemp,"CopyFiles",lpszTemp2,_MAX_LINE))
									 OR (OK == IpGetProfileString(hInf,lpszTemp,"DataSection",lpszTemp2,_MAX_LINE))
									 OR (OK == IpGetProfileString(hInf,lpszTemp,"DriverFile",lpszTemp2,_MAX_LINE))
									 OR (OK == IpGetProfileString(hInf,lpszTemp,"DataFile",lpszTemp2,_MAX_LINE)))
								{
									lstrcpyn(szSection, lpszTemp, _MAX_PATH_);
									IpClose(hInf);

                                    DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #9 Strings Found Correct Section"));

									return TRUE;
								}


								// We have a bad INF file:
								// We have the manufacturer name and the model name and an
								// install section, but it doesn't have any of the keys we need.
								// bail.
                                //
								IpClose(hInf);

                                DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #10 Strings Bad INF File"));

								return FALSE;
							}
						}
					}
				}

				if (OK != IpFindNextLine(hInf, &hInfLine))
				{
					break;
				}
			}
		}


	} // Try # 6


	// Try # 7
	// Check to see if there is a valid InstallSection.
    //
	if (OK == IpFindFirstLine(hInf, "InstallSection", NULL, &hInfLine))
	{
		// quick check of InstallSection
		if (OK == IpFindFirstLine(hInf, "InstallSection", NULL, &hInfLine))
		{
			if ((OK == IpGetProfileString(hInf,"InstallSection","CopyFiles",lpszTemp2,_MAX_LINE))
				 OR (OK == IpGetProfileString(hInf,"InstallSection","DataSection",lpszTemp2,_MAX_LINE))
				 OR (OK == IpGetProfileString(hInf,"InstallSection","DriverFile",lpszTemp2,_MAX_LINE))
				 OR (OK == IpGetProfileString(hInf,"InstallSection","DataFile",lpszTemp2,_MAX_LINE)))
			{
				lstrcpyn(szSection, "InstallSection", _MAX_PATH_);
				IpClose(hInf);

				DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #11 Found correct InstallSection"));

				return TRUE;
			}
		}

	} // Try # 7


	// Try # 8.
	// Try the [Strings] section.
	if (OK == IpFindFirstLine(hInf, "Strings", NULL, &hInfLine))
	{
		// Get the number of lines
		if (OK == IpGetLineCount(hInf, "Strings", &nCount))
		{
			for (i = 0; i < nCount; i++)
			{
				lpszBuf[0] = '\0';
				lpszTemp[0] = '\0';     
				if (OK == IpGetStringField(hInf, hInfLine, 1, lpszTemp, _MAX_LINE, &nCopied))
				{
					// Garth: Use CompareString with the 
					// IGNORE_CASE, IGNORE_KANATYPE, and IGNORE_WIDTH flags
					if (lstrcmpi(szModel, lpszTemp) == 0)
					{
						// The model name has a string var on the left
						if (OK == IpGetStringField(hInf, hInfLine, 0, lpszBuf, _MAX_LINE, &nCopied))
						{
							// There was a value on the left, try to use it
							if (OK == IpFindFirstLine(hInf, lpszBuf, NULL, &hInfLine))
							{
								// quick check of Strings 
								if (OK == IpFindFirstLine(hInf, lpszBuf, NULL, &hInfLine))
								{
									if ((OK == IpGetProfileString(hInf,lpszBuf,"CopyFiles",lpszTemp2,_MAX_LINE))
										 OR (OK == IpGetProfileString(hInf,lpszBuf,"DataSection",lpszTemp2,_MAX_LINE))
										 OR (OK == IpGetProfileString(hInf,lpszBuf,"DriverFile",lpszTemp2,_MAX_LINE))
										 OR (OK == IpGetProfileString(hInf,lpszBuf,"DataFile",lpszTemp2,_MAX_LINE)))
									{
										lstrcpyn(szSection, lpszBuf, _MAX_PATH_);
										IpClose(hInf);

                                        DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #12 Found String = Model Section"));

										return TRUE;
									}
								}
							}
						}
					}
				}
			}
		}
	} // Try # 8


	// No more ideas, Give up.
    //
	IpClose(hInf);

    DBG_MSG(DBG_LEV_INFO, ("FindCorrectSection : #13 Failed to find section"));

	return FALSE;
}


//-----------------------------------------------------------------------
// Function: GetInfOption(hInf,lpszSection,bDataSection,bLocalize,
//                        lpszDataSection,lpszKeyName,lpszBuffer,wBufSize)
//
// Action: Get the specified keyword from the INF file. Look in the
//         appropriate section(s)
//
// Note:   Keys may always appear in lpszSection, and may appear in
//         lpszDataSection if bDataSection is TRUE. If the key appears
//         in both sections, the value from lpszSection takes precedence.
//
// Return: TRUE if we got the option, FALSE if not
//-----------------------------------------------------------------------
BOOL NEAR PASCAL GetInfOption(HINF   hInf,
							LPSTR  lpszSection,
							BOOL   bDataSection,
							BOOL   bLocalize,
							LPSTR  lpszDataSection,
							LPSTR  lpszKeyName,
							LPSTR  lpszBuffer,
							WORD   wBufSize)
{
	LPSTR lpszTemp;
	BOOL  bAllocated;
	BOOL  bSuccess;

	if (bLocalize && wBufSize &&
		 (lpszTemp=(LPSTR)(HP_GLOBAL_ALLOC_DLL(max(wBufSize,_MAX_PATH_)))))
	{
		bAllocated=TRUE;
	}
	else
	{
		lpszTemp=lpszBuffer;
		bAllocated=FALSE;
	}

	if (OK == IpGetProfileString(hInf,lpszSection,lpszKeyName,lpszTemp,wBufSize))
	{
		bSuccess=TRUE;
	}
	else if (bDataSection && 
			(OK == IpGetProfileString(hInf,lpszDataSection,
					lpszKeyName,lpszTemp,wBufSize)))
	{
		bSuccess=TRUE;
	}
	else
		bSuccess=FALSE;

	if (bAllocated)
	{
		if (bSuccess)
			GenFormStrWithoutPlaceHolders(lpszBuffer,lpszTemp,hInf);

		HP_GLOBAL_FREE(lpszTemp);
	}

	return bSuccess;
}


//-----------------------------------------------------------------------
// Function: myatoi(pszInt)
//
// Yup, the first function that everyone writes!
//-----------------------------------------------------------------------
int WINAPI myatoi(
    LPSTR pszInt)
{
	int  nRet;
	char cSave;

	for (nRet = 0; ; ++pszInt) {

		if ((cSave = (*pszInt - (char)'0')) > 9)
			break;

		nRet = (nRet * 10) + cSave;
	}

	return nRet;
}


//------------------------------------------------------------------------
// Function: ZeroMem(lpData,wCount)
//
// Action: Zero-initialize wCount bytes at lpData (duh)
//
// Return: VOID
//------------------------------------------------------------------------
VOID WINAPI ZeroMem(LPVOID lpData,
						  WORD   wCount)
{
	LPBYTE lpBuf=(LPBYTE)lpData;

	while (wCount--)
	{
		*lpBuf='\0';
		lpBuf++;
	}
}


//--------------------------------------------------------------------
// Function: FlushCachedPrinterFiles(void)
//
// Action: Flush printer driver files that might be cached by the
//         system. Two caches exist--one maintained by USER, and one
//         maintained by winspl16.drv (for old drivers)
//
// Return: Void
//--------------------------------------------------------------------
VOID WINAPI FlushCachedPrinterFiles()
{
	HWND    hWndMsgSvr;
	HMODULE hModWinspl16;

	// Send a message to MSGSVR32 to flush the default printer's cached DC.
	if (hWndMsgSvr = FindWindow(cszMsgSvr, NULL))
		SendMessage(hWndMsgSvr, WM_USER+0x010A, 0, 0L);

	// If WINSPL16 is in memory, force it to flush its cache
	if (hModWinspl16 = GetModuleHandle(cszWinspl16))
	{
		WEPPROC fpWep;

		if (fpWep = (WEPPROC)GetProcAddress(hModWinspl16, "WEP"))
		{
			fpWep(0);
		}
	}
}


//------------------------------------------------------------------
// Function: RenameFailed(lpsi,lpFileSpec)
//
// Action: Handle the case where the rename failed. Give the user a
//         chance to close existing apps and shut down printers.
//
// Return: 0 if the file isn't loaded (use default handling)
//         VCPN_ABORT if file is in use & user cancelled
//         VCPN_RETRY if file is in use & user wants to retry
//------------------------------------------------------------------
LRESULT NEAR PASCAL RenameFailed(LPSI lpsi,
											LPVCPFILESPEC lpFileSpec)
{
	char szFile[_MAX_PATH_];

	if (vsmGetStringName(lpFileSpec->vhstrFileName,szFile,sizeof(szFile)) &&
		 GetModuleHandle(szFile))
	{
		char Message[_MAX_PATH_];


		// File is in use by the system--give the user the chance to shut
		// down existing applications, then continue.

		if ( LoadString(g_hInst, IDS_ERR_FILE_IN_USE, Message, _MAX_PATH_) IS 0)
		{
            DBG_MSG(DBG_LEV_INFO, ("RenameFailed : Could not load string, file in use"));

			lstrcpy(Message, cszFileInUse);
		}
		if (IDCANCEL IS MessageBox(NULL, Message, szFile,
											MB_ICONEXCLAMATION|MB_RETRYCANCEL|MB_DEFBUTTON2))
		{
			return VCPN_ABORT;
		}
		else
		{
			// Flush any cached printers & retry
			FlushCachedPrinterFiles();
			return VCPN_RETRY;
		}
	}

	return 0;
}


//------------------------------------------------------------------
// Function: MyVcpCallbackProc(lpvObj,uMsg,wParam,lParam,lparamRef)
//
// Action: This simply passes through to vcpUICallbackProc, with one
//         exception: If a file is in use in the system, we warn the
//         user and give them a chance to close existing apps.
//
// Return: Whatever RenameFailed or vcpUICallbackProc return.
//------------------------------------------------------------------
LRESULT CALLBACK MyVcpCallbackProc(LPVOID lpvObj,
											  UINT   uMsg,
											  WPARAM wParam,
											  LPARAM lParam,
											  LPSI   lpsi)
{
	LRESULT lResult = 0;

	if ((VCPM_FILERENAME + VCPM_ERRORDELTA) IS uMsg)
		lResult = RenameFailed(lpsi,(LPVCPFILESPEC)lpvObj);

	if (! lResult)
	{
		lResult = vcpUICallbackProc(lpvObj,uMsg,wParam,lParam,
											 (LPARAM)lpsi->lpVcpInfo);
	}

	return lResult;
}


//-------------------------------------------------------------------
// Function: OpenQueue(lpsi, lpVcpInfo, lpbOpened)
//
// Action: Initialize lpVcpInfo and open the queue. *lpbOpened reflects
//         whether or not we actually opened the queue.
//
// Return: TRUE if successful, FALSE if not.
//-------------------------------------------------------------------
BOOL NEAR PASCAL OpenQueue(LPSI        lpsi,
									LPVCPUIINFO lpVcpInfo,
									BOOL FAR   *lpbOpened)
{
	BOOL bSuccess = FALSE;

	// Initialize the structure
	ZeroMem(lpVcpInfo, sizeof(VCPUIINFO));

	lpVcpInfo->flags = VCPUI_CREATEPROGRESS;
	lpVcpInfo->hwndParent = NULL;

	lpsi->lpVcpInfo = (LPBYTE) lpVcpInfo;

	// Open the queue
	if (RET_OK IS VcpOpen((VIFPROC)MyVcpCallbackProc, (LPARAM)lpsi))
	{
		bSuccess = TRUE;
		*lpbOpened = TRUE;
	}
	else
	{
		// Couldn't open the queue, so clear lpVcpInfo
		lpsi->lpVcpInfo = NULL;
		*lpbOpened = FALSE;
	}

	return bSuccess;
}


//-------------------------------------------------------------------
// Function: GenInstallCallback(lpGenInfo,lpsi)
//
// Action: This gets called for each file to be queued. Add it to
//         the list identified by lpsi->lpFiles, then queue it
//         to be copied to the system directory
//
// Return: GENN_SKIP if it's a copy (we queue it ourselves), 
//         GENN_OK otherwise.
//-------------------------------------------------------------------
LRESULT WINAPI GenInstallCallback(LPGENCALLBACKINFO lpGenInfo,
											 LPSI lpsi)
{
	int wLength;
	int wMaxLength;
	int wChunk;

	if (GENO_COPYFILE != lpGenInfo->wOperation)
		return GENN_OK;

	// Save the dependent file in lpsi->lpFiles. wFilesAllocated and
	// wFilesUsed are used to keep track of memory usage.
	wLength = lstrlen(lpGenInfo->pszDstFileName);

	// Pad the length if it's going to LDID_COLOR
	if (LDID_COLOR IS lpGenInfo->ldidDst)
		wMaxLength = wLength + MAX_COLOR_PATH;
	else
		wMaxLength = wLength;

	wChunk = max(256,wMaxLength);

	// This is where we allocate lpFiles the first time
	if (! lpsi->lpFiles)
	{
		if (lpsi->lpFiles = (unsigned char *)HP_GLOBAL_ALLOC_DLL(wChunk))
		{
			lpsi->wFilesAllocated = wChunk;
		}
		else
		{
			return GENN_SKIP;
		}
	}

	// double NULL terminated
	if ((lpsi->wFilesUsed + wMaxLength + 2) > lpsi->wFilesAllocated)
	{
		LPSTR lpNew;

		lpNew = (unsigned char *)HP_GLOBAL_REALLOC_DLL(lpsi->lpFiles,
																	  lpsi->wFilesAllocated + wChunk, GMEM_ZEROINIT);

		if (lpNew)
		{
			lpsi->wFilesAllocated += wChunk;
			lpsi->lpFiles = (unsigned char *) lpNew;
		}
		else
		{
			return GENN_SKIP;
		}
	}

	// Add this file to the list && ensure that it's doubly-NULL
	// terminated. If it's going to LDID_COLOR, prepend "COLOR\"
	// to the string

	if (LDID_COLOR IS lpGenInfo->ldidDst)
	{
		char ColorPath[MAX_COLOR_PATH];
		if ( LoadString(g_hInst, IDS_COLOR_PATH, ColorPath, MAX_COLOR_PATH) IS 0)
		{
			strcpy(ColorPath, "COLOR\\");
		}

		strcpy((LPSTR)(lpsi->lpFiles + lpsi->wFilesUsed), ColorPath);
		lpsi->wFilesUsed += strlen(ColorPath);
	}

	strcpy((LPSTR)(lpsi->lpFiles + lpsi->wFilesUsed), lpGenInfo->pszDstFileName);
	lpsi->wFilesUsed += (wLength + 1);
	lpsi->lpFiles[lpsi->wFilesUsed] = '\0';

	return GENN_SKIP;
}



//-------------------------------------------------------------------
// Function: QueueNewInf(lpsi, lpdn)
//
// Action: Call GenInstallEx to queue up the files we need to copy.
//
// Return: TRUE if we should continue, FALSE if not.
//-------------------------------------------------------------------
BOOL WINAPI QueueNewInf(LPSI lpsi, LPDRIVER_NODE lpdn)
{
	if (lpdn)
	{
		return (RET_OK == GenInstallEx((HINF)lpsi->hModelInf,
												 lpdn->lpszSectionName, GENINSTALL_DO_FILES, NULL, 
												 (GENCALLBACKPROC)GenInstallCallback, (LPARAM)lpsi));
	}

	return FALSE;
}

//-------------------------------------------------------------------
// Function: CloseQueue(lpsi, bSuccessSoFar, bOpened)
// 
// Action: Close the VCP queue and report any errors to the user.
//         If bSuccessSoFar is FALSE, abandon the queue & return FALSE
//
// Return: TRUE if the copy was successful, FALSE if not.
//-------------------------------------------------------------------
BOOL NEAR PASCAL CloseQueue(LPSI lpsi,
									 BOOL bSuccessSoFar,
									 BOOL bOpened)
{
	BOOL   bSuccess = FALSE;

	if (! bOpened)
		bSuccess = bSuccessSoFar;
	else
	{
		if (! bSuccessSoFar)
			VcpClose(VCPFL_ABANDON, NULL);
		else if (lpsi->bDontQueueFiles)
		{
			VcpClose(VCPFL_ABANDON, NULL);
			bSuccess=TRUE;
		}

		// The queue is now closed, so clear lpsi->lpVcpInfo
		if (lpsi->lpVcpInfo)
			lpsi->lpVcpInfo = NULL;
	}

	return bSuccess;
}



//-----------------------------------------------------------------------
// Function: WrapVcpCopy(lpsi,lpfnQueueFunction,lpdn)
//
// Action: Wrap lpfnQueueFunction inside calls to open & close the
//         queue. If lpsi->bDontCopyFiles is TRUE, we'll close the
//         queue without copying anything.
//
// Return: TRUE if everything went smoothly, FALSE if not.
//-----------------------------------------------------------------------
BOOL WINAPI WrapVcpCopy(LPSI lpsi,
								LPQUEUEPROC lpfnQueueFunction,
								LPDRIVER_NODE lpdn)

{
	VCPUIINFO VcpInfo;
	BOOL      bSuccess=FALSE;
	BOOL      bOpened=FALSE;

	if (OpenQueue(lpsi, &VcpInfo, &bOpened))
	{
		BOOL bQueuedOK;

		bQueuedOK = lpfnQueueFunction(lpsi, lpdn);

		bSuccess = CloseQueue(lpsi, bQueuedOK, bOpened);
	}

	return bSuccess;
}


//--------------------------------------------------------------------
// Function: PrintLPSI(lpsi)
//
// Action: Print out the contents of the LPSI
//
// Side effect: None
//
// Return: VOID
//
//--------------------------------------------------------------------
VOID PrintLPSI(LPSI lpsi)
{
    DBG_MSG(DBG_LEV_INFO, ("LPSI : dwDriverVersion   : %#lX", (DWORD)lpsi->dwDriverVersion));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : dwUniqueID        : %d"  , lpsi->dwUniqueID));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : bNetPrinter       : %d"  , lpsi->bNetPrinter));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : wFilesUsed        : %d"  , lpsi->wFilesUsed));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : wFilesAllocated   : %d"  , lpsi->wFilesAllocated));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : wRetryTimeout     : %d"  , lpsi->wRetryTimeout));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : wDNSTimeout       : %d"  , lpsi->wDNSTimeout));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : bDontQueueFiles   : %d"  , lpsi->bDontQueueFiles));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : bNoTestPage       : %d"  , lpsi->bNoTestPage));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : hModelInf         : %0X" , lpsi->hModelInf));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : lpPrinterInfo2    : %d"  , lpsi->lpPrinterInfo2));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : lpDriverInfo3     : %d"  , lpsi->lpDriverInfo3));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szFriendly        : %s"  , lpsi->szFriendly));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szModel           : %s"  , lpsi->szModel));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szDefaultDataType : %s"  , lpsi->szDefaultDataType));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szBinName         : %s"  , lpsi->BinName));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szShareName       : %s"  , lpsi->ShareName));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : INFfileName       : %s"  , lpsi->INFfileName));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szPort            : %s"  , lpsi->szPort));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szDriverFile      : %s"  , lpsi->szDriverFile));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szDataFile        : %s"  , lpsi->szDataFile));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szConfigFile      : %s"  , lpsi->szConfigFile));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szHelpFile        : %s"  , lpsi->szHelpFile));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szPrintProcessor  : %s"  , lpsi->szPrintProcessor));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szVendorSetup     : %s"  , lpsi->szVendorSetup));
    DBG_MSG(DBG_LEV_INFO, ("LPSI : szVendorInstaller : %s"  , lpsi->szVendorInstaller));
}


//-----------------------------------------------------------------------
// Function: FindSelectedDriver(lpsi, lpdn)
//
// Action: Try to find the install section associated with the specified
//         model. If the currently selected driver matches the model, we
//         get all the required info from it.
//
// Note:   Upon entry
//                 lpdn->lpszSectionName
//                 has the name of the correct section in the INF file.
//
// Return: TRUE is successful, FALSE if not.
//-----------------------------------------------------------------------
BOOL WINAPI FindSelectedDriver(
    LPSI          lpsi,
    LPDRIVER_NODE lpdn)
{
	HINFLINE hInfDummy;
	HINF     myHINF;
	WORD     wTest;
	BOOL     bDataSection=FALSE;
	BOOL     bQueuedOK;
	char     szData[_MAX_PATH_];
	char     szTimeout[10];
	char     lpSection[_MAX_PATH_];

	// Extract all of our information from the INF file.
	// Open the INF file.
	if (OK != IpOpen(lpsi->INFfileName, &myHINF))
	{
		return FALSE;
	}

	lstrcpyn(lpSection, lpdn->lpszSectionName, _MAX_PATH_);

	if (OK != IpFindFirstLine(myHINF, lpSection, NULL, &hInfDummy))
	{
		IpClose(myHINF);
		return FALSE;
	}
	lpsi->hModelInf = (int) myHINF;

	// Build the dependent file list now.
	lpsi->bDontQueueFiles=TRUE;
	bQueuedOK = WrapVcpCopy(lpsi,QueueNewInf,lpdn);
	lpsi->bDontQueueFiles = FALSE;

	if (!bQueuedOK)
	{
		IpClose(myHINF);
		lpsi->hModelInf = 0;
		return FALSE;
	}


	if (!lpsi->wFilesUsed)
	{
		IpClose(myHINF);
		lpsi->hModelInf = 0;
		return FALSE;
	}

	// Check for a data section. If none is specified, then the
	// data section is the section associated with this device.
	// The DataSection key can only appear in the installer section
	// (for obvious reasons).
	bDataSection=GetInfOption(myHINF,lpSection,FALSE,FALSE,NULL,
									  cszDataSection,szData,sizeof(szData));

	// Don't change szData below this line! (It may contain the
	// data section name)

	// Get the driver name (default is the primary section name)
	if (!GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
							cszDriverFile,lpsi->szDriverFile,sizeof(lpsi->szDriverFile)))
	{
		lstrcpyn(lpsi->szDriverFile,lpSection,sizeof(lpsi->szDriverFile));
	}

	// Get the data file name (default is the primary section name)
	if (!GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
							cszDataFile,lpsi->szDataFile,sizeof(lpsi->szDataFile)))
	{
		lstrcpyn(lpsi->szDataFile,lpSection,sizeof(lpsi->szDataFile));
	}

	// Get the config file name (default is the driver name)
	if (!GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
							cszConfigFile,lpsi->szConfigFile,sizeof(lpsi->szConfigFile)))
	{
		lstrcpyn(lpsi->szConfigFile,lpsi->szDriverFile,
					sizeof(lpsi->szConfigFile));
	}

	// Get the help file (default is none)
	GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
					 cszHelpFile,lpsi->szHelpFile,sizeof(lpsi->szHelpFile));

	// Get the Print Processor (default comes from resources)
	if (!GetInfOption(myHINF,lpSection,bDataSection,TRUE,szData,
							cszPrintProcessor,lpsi->szPrintProcessor,
							sizeof(lpsi->szPrintProcessor)))
	{
		if (LoadString(g_hInst,IDS_DEFAULT_PRINTPROCESSOR,
							lpsi->szPrintProcessor,sizeof(lpsi->szPrintProcessor)) IS 0)
		{
            DBG_MSG(DBG_LEV_INFO, ("FindSelectedDriver : Could not load string, default print processor"));

			lstrcpy(lpsi->szPrintProcessor, cszDefaultPrintProcessor);
		}
	}

	// Get the Default Data Type (default comes from resources)
	if (!GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
							cszDefaultDataType,lpsi->szDefaultDataType,
							sizeof(lpsi->szDefaultDataType)))
	{
		if ( LoadString(g_hInst,IDS_DEFAULT_DATATYPE,
							 lpsi->szDefaultDataType,sizeof(lpsi->szDefaultDataType)) IS 0)
		{
            DBG_MSG(DBG_LEV_INFO, ("FindSelectedDriver : Could not load string, default data type"));

			lstrcpy(lpsi->szDefaultDataType, cszMSDefaultDataType);
		}
	}

	// Get the Vendor Setup (default is none)
	GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
					 cszVendorSetup,lpsi->szVendorSetup,sizeof(lpsi->szVendorSetup));

	// Get the Vendor Installer (default is none)
	GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
					 cszVendorInstaller,lpsi->szVendorInstaller,
					 sizeof(lpsi->szVendorInstaller));

	// Get the device timeouts (ClearPrinterInfo defaults to 15 & 45)
	if (GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
						  cszRetryTimeout,szTimeout,sizeof(szTimeout)))
	{
		if (wTest=myatoi(szTimeout))
			lpsi->wRetryTimeout=wTest;
	}

	if (GetInfOption(myHINF,lpSection,bDataSection,FALSE,szData,
						  cszNotSelectedTimeout,szTimeout,sizeof(szTimeout)))
	{
		if (wTest=myatoi(szTimeout))
			lpsi->wDNSTimeout=wTest;
	}

	// Decide whether or not we should use the test page. Test page is
	// skipped if the INF specifically requests it, if we have a
	// vendor-supplied DLL, or if the port is in conflict.
	if ( (lpsi->szVendorSetup[0]) ||
		 (lpsi->szVendorInstaller[0]) || 
		  GetInfOption(myHINF, lpSection, bDataSection, FALSE, szData,
			cszNoTestPage, szTimeout, sizeof(szTimeout)))
	{
		lpsi->bNoTestPage=TRUE;
	}

	IpClose(myHINF);
	lpsi->hModelInf = 0;

	return TRUE;
}


//--------------------------------------------------------------------
// Function: CreateDriverNode()
//
// Action: Create a driver node and initialize it..  
//
// Side effect: Allocates memory that must be freed by caller..
//
// Return: RET_OK on success.
//         
//--------------------------------------------------------------------
RETERR WINAPI CreateDriverNode(
    LPLPDRIVER_NODE lplpdn,
    UINT     Rank,
    UINT     InfType,
    unsigned InfDate,
    LPCSTR   lpszDevDescription,
    LPCSTR   lpszDrvDescription,
    LPCSTR   lpszProviderName,
    LPCSTR   lpszMfgName,
    LPCSTR   lpszInfFileName,
    LPCSTR   lpszSectionName,
    DWORD    dwPrivateData)
{
	int   DrvDescLen = 0;
	int   DevDescLen = 0;
	int   SectionLen = 0;
	LPSTR lpszTemp;


	// Compute a allocate space for the variable part of the DRIVER node
    //
	DrvDescLen = lstrlen(lpszDrvDescription);
	DevDescLen = lstrlen(lpszDevDescription);
	SectionLen = lstrlen(lpszSectionName);

	*lplpdn = (LPDRIVER_NODE) HP_GLOBAL_ALLOC_DLL(sizeof(DRIVER_NODE) + 
					 DrvDescLen + DevDescLen + SectionLen + 
					 (2 * MAX_DEVICE_ID_LEN) + _MAX_PATH_ + NSTRINGS);

	if (*lplpdn == NULL)
	{
		return(ERR_DI_LOW_MEM);
	}
	else
	{
		(*lplpdn)->Rank    = Rank;
		(*lplpdn)->InfType = InfType;
		(*lplpdn)->InfDate = InfDate;

		// For compatibility copy the DevDescription into lpszDescription
		lpszTemp = (LPSTR)((DWORD)(*lplpdn) + sizeof(DRIVER_NODE));
		lstrcpy(lpszTemp, lpszDevDescription);
		(*lplpdn)->lpszDescription = lpszTemp;

		// New.  Copy the Drv description into the lpszDrvDescription
		lpszTemp = (LPSTR)((DWORD)(lpszTemp) + (DWORD)(DevDescLen + 1));
		lstrcpy(lpszTemp, lpszDevDescription);
		(*lplpdn)->lpszDrvDescription = lpszTemp;

		lpszTemp = (LPSTR)((DWORD)(lpszTemp) + (DWORD)(DrvDescLen + 1));
		lstrcpy(lpszTemp, lpszSectionName);
		(*lplpdn)->lpszSectionName = lpszTemp;

		// Init HardwareID and CompatIDs buffers to empty strings

		lpszTemp = (LPSTR)((DWORD)(lpszTemp) + (DWORD)(SectionLen + 1));
		*lpszTemp = '\0';
		(*lplpdn)->lpszHardwareID = lpszTemp;

		lpszTemp = (LPSTR)((DWORD)(lpszTemp) + (DWORD)(MAX_DEVICE_ID_LEN + 1));
		*lpszTemp = '\0';
		(*lplpdn)->lpszCompatIDs = lpszTemp;

		(*lplpdn)->atInfFileName = GlobalAddAtom(lpszInfFileName);

		if (lpszMfgName != NULL) {
			(*lplpdn)->atMfgName = GlobalAddAtom(lpszMfgName);
		}
		if (lpszProviderName != NULL) {
			(*lplpdn)->atProviderName = GlobalAddAtom(lpszProviderName);
		}

		return(RET_OK);
	}
}


//--------------------------------------------------------------------
//
// Function: HaveAllFiles(lpsi)
//
// Action: Do we have all printer driver dependent files in this directory?
//
// Return: TRUE if all found, FALSE if not
//
//--------------------------------------------------------------------
BOOL WINAPI HaveAllFiles(LPSI lpsi, LPSTR cszPath)
{
	int     result;
	LPSTR   lpThisFile;
	struct stat statBuf;
	char    fileSpec[_MAX_PATH_];
	BOOL    fileNotFound = FALSE;


	// Verify that all the dependent files are in the directory
	// pointed to by cszPath.
    //
	for (lpThisFile = (char *)lpsi->lpFiles; *lpThisFile; 
		 lpThisFile += (lstrlen(lpThisFile) + 1))
	{
		// Get data associated with file
		lstrcpy(fileSpec, cszPath);
		lstrcat(fileSpec, cszBackslash);
		lstrcat(fileSpec, lpThisFile);
		result = stat( fileSpec, (struct stat *)&statBuf);

		// Check if statistics are valid:
		if ( result ISNT 0 )
		{
            DBG_MSG(DBG_LEV_INFO, ("HaveAllFiles : File not found <%s>", fileSpec));

			fileNotFound = TRUE;
			break;
		}
	}

    return !fileNotFound;
}


//--------------------------------------------------------------------
//
// Function: CopyNeededFiles(lpsi, cszPath)
//
// Action: Copy all printer driver files for this printer from the 
//                      source directory to the requied printer driver directory.
//
// Return:      RET_OK on success.
//                      RET_FILE_COPY_ERROR is there is a file copy error
//
//--------------------------------------------------------------------
DWORD WINAPI CopyNeededFiles(LPSI lpsi, LPSTR cszPath)
{
	DWORD    dwResult = RET_OK;
    LONG     lResult;
    LPSTR    lpThisFile;
    OFSTRUCT ofStrSrc;
    OFSTRUCT ofStrDest;
    HFILE    hfSrcFile, hfDstFile;
    char     fileSource[_MAX_PATH_];
    char     fileDest[_MAX_PATH_];
    char     destDir[_MAX_PATH_];

    DBG_MSG(DBG_LEV_INFO, ("CopyNeededFiles : About to flush printer files"));

    lstrcpy(destDir, lpsi->szDriverDir);

    if (lstrcmpi(destDir, cszPath) IS 0) {

    // all required files are in the destination directory
        //
        return RET_OK;
    }

    // Flush any cached files the system may be holding
    //
    FlushCachedPrinterFiles();

    LZStart();

    for (lpThisFile = (char *)lpsi->lpFiles; *lpThisFile;
        lpThisFile += (lstrlen(lpThisFile) + 1)) {

        lstrcpy(fileSource, cszNull);
        lstrcat(fileSource, cszPath);
        lstrcat(fileSource, cszBackslash);
        lstrcat(fileSource, lpThisFile);

        lstrcpy(fileDest, cszNull);
        lstrcat(fileDest, destDir);
        lstrcat(fileDest, cszBackslash);
        lstrcat(fileDest, lpThisFile);

        lResult = -1;

		if (hfSrcFile = LZOpenFile(fileSource, &ofStrSrc, OF_READ)) {

		    if (hfDstFile = LZOpenFile(fileDest, &ofStrDest, OF_CREATE)) {
#if 0
                lResult = CopyLZFile(hfSrcFile, hfDstFile);
#else
                // Hack.  If spooler has a file locked, then this call
                // would fail resulting in the class-installer being called
                // to copy the files.  But for some reason, the class-installer
                // prompts a path-dialog wanting to know where the files
                // are.  Several attempts at fixing up the device-info
                // struct with path-information failed to remedy this
                // situation.  Therefore, for now, we will prevent this
                // CopyLZFile() from returning error to keep away from
                // the class-installer until this problem can be figured
                // out.
                //
                // See the caller of this routine to see how the class-
                // installer is called.
                //
	            CopyLZFile(hfSrcFile, hfDstFile);

                lResult = 0;
#endif
                LZClose(hfDstFile);

            } else {

                DBG_MSG(DBG_LEV_INFO, ("CopyNeededFiles : Could not create dst-file <%s>", fileDest));
            }

            LZClose(hfSrcFile);

        } else {

            DBG_MSG(DBG_LEV_INFO, ("CopyNeededFiles : Could not create src-file <%s>", fileSource));
        }

        if (lResult < 0) {

            dwResult = RET_FILE_COPY_ERROR;
            break;
        }
    }

    LZDone();

    return dwResult;
}



//--------------------------------------------------------------------
// Function: ParseINF16(LPSI)
//
// Action: Parse the INF file and store required info in LPSI.
//
// Return:      RET_OK on success.
//                      RET_SECT_NOT_FOUND if no install section for this model
//                      RET_DRIVER_NODE_ERROR if memory error
//                      RET_INVALID_INFFILE other problem with INF file
//         
//--------------------------------------------------------------------
RETERR FAR PASCAL ParseINF16(LPSI lpsi)
{
	RETERR			ret = RET_OK;
	LPDRIVER_NODE   lpdn = NULL;
	char			szManufacturer[_MAX_PATH_];
	char			szSection[_MAX_PATH_];
	char			cszPath[_MAX_PATH_];

	// Get the manufacturer
    //
	lstrcpyn(szManufacturer,lpsi->szModel,sizeof(szManufacturer));
	lstrtok(szManufacturer,cszSpace);

    DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : Search inf<%s> mfg<%s> model<%s>", lpsi->INFfileName, szManufacturer, lpsi->szModel));


	// Get the correct section name
	if (!FindCorrectSection(lpsi->INFfileName, szManufacturer, lpsi->szModel, szSection))
	{   
		if (lstrcmpi(szManufacturer, "HP") IS 0)
		{
			lstrcpy(szManufacturer, "Hewlett-Packard");

			if (!FindCorrectSection(lpsi->INFfileName, szManufacturer, lpsi->szModel, szSection))
			{
                DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindCorrectSection Failed"));

				return RET_SECT_NOT_FOUND;

			} else {

                DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindCorrectSection Succeeded"));
            }

		} else {

            DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindCorrectSection Failed"));

			return RET_SECT_NOT_FOUND;
		}

	} else {

        DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindCorrectSection Succeeded"));
	}


	// Create a driver node
    //
	if (RET_OK != CreateDriverNode(&lpdn,
                                   0,
                                   INFTYPE_TEXT,
                                   NULL,
                                   lpsi->szModel,
                                   cszNull,
                                   cszNull,
                                   szManufacturer,
                                   lpsi->INFfileName,
                                   szSection,
                                   0))
	{
        DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : CreateDriverNode Failed"));

		return RET_DRIVER_NODE_ERROR;

	} else {

        DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : CreateDriverNode Succeeded"));
	}


	// Find the selected driver and dependent files in the INF.  Store in lpsi.
    //
	if (! FindSelectedDriver(lpsi, lpdn)) {

        DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindSelectedDriver Failed"));

		if (lpdn->atInfFileName ISNT 0)
			GlobalDeleteAtom(lpdn->atInfFileName);
		if (lpdn->atMfgName ISNT 0)
			GlobalDeleteAtom(lpdn->atMfgName);
		if (lpdn->atProviderName ISNT 0)
			GlobalDeleteAtom(lpdn->atProviderName);

		HP_GLOBAL_FREE(lpdn);

		return RET_INVALID_INFFILE;

	} else {

        DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : FindSelectedDriver Succeeded"));
    }


	// If there is not a driver already installed for this printer,
	// Copy the driver files to the driver directory.
	if (lpsi->wCommand IS CMD_INSTALL_DRIVER)
	{

		if ( getcwd(cszPath, _MAX_PATH_) IS NULL)
			lstrcpy(cszPath, ".");

		if ((HaveAllFiles(lpsi, cszPath) IS TRUE)
			 AND
			 (CopyNeededFiles(lpsi, cszPath) IS RET_OK)
			)
		{
			// We have all the needed files in the directory pointed to by cszPath.
			// They have been copied to the printer driver directory.
			ret = RET_OK;

            DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : All files copied to final directory"));

		} else {

#define HKEY_LOCAL_MACHINE          (( HKEY ) 0x80000002 )
			DEVICE_INFO             *lpdi;


			ret = DiCreateDeviceInfo(&lpdi,
                                     lpsi->szModel,
                                     0,
                                     HKEY_LOCAL_MACHINE,
                                     NULL,
							         "Printer",
                                     NULL);

			// Install the driver files.  This will only copy files that
			// need to be copied.
            //
			if (ret IS OK) {

				LPDRIVER_NODE oldDN = NULL;
				oldDN = lpdi->lpSelectedDriver;
				lpdi->lpSelectedDriver = lpdn;
				ret = DiCallClassInstaller(DIF_INSTALLDEVICEFILES, lpdi);
				lpdi->lpSelectedDriver = oldDN;
				oldDN = NULL;
				DiDestroyDeviceInfoList(lpdi);
				lpdi = NULL;
			}

			switch (ret) {

			case OK:
			case ERR_DI_NOFILECOPY:
				ret = RET_OK;
				break;
			case ERR_DI_USER_CANCEL:
				ret = RET_USER_CANCEL;
				break;
			case ERR_DI_LOW_MEM:
				ret = RET_ALLOC_ERR;
				break;
			case ERR_DI_BAD_INF:
				ret = RET_INVALID_INFFILE;
				break;

			case ERR_DI_INVALID_DEVICE_ID:
			case ERR_DI_INVALID_COMP_DEVICE_LIST:
			case ERR_DI_REG_API:							// Error returned by Reg API.
			case ERR_DI_BAD_DEV_INFO:						// Device Info struct invalid
			case ERR_DI_INVALID_CLASS_INSTALLER:			// Registry entry / DLL invalid
			case ERR_DI_DO_DEFAULT:							// Take default action
			case ERR_DI_BAD_CLASS_INFO:						// Class Info Struct invalid
			case ERR_DI_BAD_MOVEDEV_PARAMS:					// Bad Move Device Params struct
			case ERR_DI_NO_INF:								// No INF found on OEM disk
			case ERR_DI_BAD_PROPCHANGE_PARAMS:				// Bad property change param struct
			case ERR_DI_BAD_SELECTDEVICE_PARAMS:			// Bad Select Device Parameters
			case ERR_DI_BAD_REMOVEDEVICE_PARAMS:			// Bad Remove Device Parameters
			case ERR_DI_BAD_ENABLECLASS_PARAMS:				// Bad Enable Class Parameters
			case ERR_DI_FAIL_QUERY:							// Fail the Enable Class query
			case ERR_DI_API_ERROR:							// DI API called incorrectly
			case ERR_DI_BAD_PATH:							// An OEM path was specified incorrectly
			default:
				ret = RET_BROWSE_ERROR;
				break;
			}


            DBG_MSG(DBG_LEV_INFO, ("ParseINF16 : DiCallClassInstaller %s", (ret IS OK ? "succeded" : "failed")));
		}

	} // if wCommand IS CMD_INSTALL_DRIVER


	// We don't need the driver node anymore
	// Clean up and delete
    //
	if (lpdn->atInfFileName ISNT 0)
		GlobalDeleteAtom(lpdn->atInfFileName);
	if (lpdn->atMfgName ISNT 0)
		GlobalDeleteAtom(lpdn->atMfgName);
	if (lpdn->atProviderName ISNT 0)
		GlobalDeleteAtom(lpdn->atProviderName);
	HP_GLOBAL_FREE(lpdn);

	return ret;
}



/*****************************************************************************\
* LibMain
*
*   Entry-point initialization.
*
\*****************************************************************************/
#if 0
int CALLBACK LibMain(HANDLE hModule,
					 WORD   wDataSeg,
					 WORD   cbHeapSize,
					 LPSTR  lpszCmdLine)
{
	g_hInst = NULL;
	g_hInst = (HINSTANCE) hModule;
	return 1;
}

#else

BOOL FAR PASCAL LibMain(
    HANDLE hInst,
    int    nAttach,
    LPVOID pContext)
{
    if (g_hInst == NULL) {

        g_hInst = (HINSTANCE)hInst;

        if (InitStrings())
            return thk_ThunkConnect16(cszDll16, cszDll32, hInst, 1);

    }

    return FALSE;
}


#endif

/*****************************************************************************\
* Windows Exit Proceedure (WEP)
*
*
\*****************************************************************************/
int CALLBACK WEP(int exportType)
{
    FreeStrings();

	return 1;
}