#include <windows.h>
#if 0
#include <stdtypes.h>
#endif
#include <stdio.h>
#include <stdlib.h>
#include <direct.h>
#include <dos.h>
#include <sys\types.h>
#include <sys\stat.h>
#include <ctype.h>      /* isspace */
#include <io.h>
#include <limits.h>     /* UINT_MAX */
#include <memory.h>     /* _fmemcpy, _fmemccpy */
#include <lzexpand.h>
#include <shellapi.h>   /* HKEY, HKEY_CLASSES_ROOT, ERROR_SUCCESS */
#include "setup.h"
#include "genthk.h"     /* thunks for calls to get 32-bit version */
#include "driveex.h"
#include <stdtypes.h>

/* Messages for optional background task.
*/
#define IDM_ACME_STARTING 261
#define IDM_ACME_COMPLETE 262
#define IDM_ACME_FAILURE  263

#ifdef APPCOMP
#include <decomp.h>
#endif /* APPCOMP */
#include <fdi.h>


/* List file extension */
char szLstExt[] = "LST";


/* List file section names */
char szDefaultParamsSect[] = "Params";
char szDefaultFilesSect[]  = "Files";

char szWin3xParamsSect[]   = "Win3.x Params";
char szWin3xFilesSect[]    = "Win3.x Files";

char szWin95ParamsSect[]   = "Windows 95 Params";
char szWin95FilesSect[]    = "Windows 95 Files";

char szNTAlphaParamsSect[] = "NT Alpha Params";
char szNTAlphaFilesSect[]  = "NT Alpha Files";

char szNTMipsParamsSect[]  = "NT Mips Params";
char szNTMipsFilesSect[]   = "NT Mips Files";

char szNTPpcParamsSect[]   = "NT PPC Params";
char szNTPpcFilesSect[]    = "NT PPC Files";

char szNTIntelParamsSect[] = "NT Intel Params";
char szNTIntelFilesSect[]  = "NT Intel Files";

char szNTVerIntelParamsSect[] = "NT3.51 Intel Params";
char szNTVerIntelFilesSect[]  = "NT3.51 Intel Files";

char * szParamsSect = szNull;
char * szFilesSect  = szNull;

typedef struct _PLATFORM_SPEC
{
    BYTE minMajorVersion;
    BYTE minMinorVersion;
    char *szParamsSect;
    char *szFilesSect;
} PLATFORM_SPEC, *PPLATFORM_SPEC;

PLATFORM_SPEC aIntelSpec[] =
{
    {3, 51, szNTVerIntelParamsSect, szNTVerIntelFilesSect},
    {0,  0, szNTIntelParamsSect,    szNTIntelFilesSect},
    {0,  0, NULL,           NULL}
};

PLATFORM_SPEC aAlphaSpec[] =
{
    {0,  0, szNTAlphaParamsSect,    szNTAlphaFilesSect},
    {3, 51, szNTVerIntelParamsSect, szNTVerIntelFilesSect},
    {0,  0, szNTIntelParamsSect,    szNTIntelFilesSect},
    {0,  0, NULL,           NULL}
};

PLATFORM_SPEC aMipsSpec[] =
{
    {0,  0, szNTMipsParamsSect,     szNTMipsFilesSect},
    {0,  0, NULL,           NULL}
};

PLATFORM_SPEC aPpcSpec[] =
{
    {0,  0, szNTPpcParamsSect,      szNTPpcFilesSect},
    {0,  0, NULL,           NULL}
};


PLATFORM_SPEC aEmptySpec[] =
{
    {0,  0, NULL,           NULL}
};

// Note: this is indexed by PROCESSOR_ARCHITECTURE_xxx
//       definitions in ntexapi.h
//
// Note: may want to add extra sections for IA64 and AXP64
PPLATFORM_SPEC aaPlatformSpecs[] =
{
    aIntelSpec,     // PROCESSOR_ARCHITECTURE_INTEL 0
    aMipsSpec,      // PROCESSOR_ARCHITECTURE_MIPS  1
    aAlphaSpec,     // PROCESSOR_ARCHITECTURE_ALPHA 2
    aPpcSpec,       // PROCESSOR_ARCHITECTURE_PPC   3
    aEmptySpec,     // PROCESSOR_ARCHITECTURE_SHX   4
    aEmptySpec,     // PROCESSOR_ARCHITECTURE_ARM   5
    aIntelSpec,     // PROCESSOR_ARCHITECTURE_IA64  6
    aAlphaSpec,     // PROCESSOR_ARCHITECTURE_ALPHA64 7
};


/* CPU environment variable and values */
/*  (NOTE: These must be upper case)   */
char szCpuEnvVarName[]     = "PROCESSOR_ARCHITECTURE";
char szIntelEnvVarVal[]    = "X86";
char szIA64EnvVarVal[]     = "IA64";
char szAlphaEnvVarVal[]    = "ALPHA";
char szAXP64EnvVarVal[]    = "AXP64";
char szMipsEnvVarVal[]     = "MIPS";
char szPpcEnvVarVal[]      = "PPC";


/* Bootstrapper class name */
char szBootClass[]  = "STUFF-BOOT";

/* String buffer sizes */
#define cchLstLineMax       128
#define cchWinExecLineMax   (256 + cchFullPathMax)

/* No. of retries to attempt when removing files or dirs,
*   or when executing a chmod.
*/
#define cRetryMax   1200

/* SetErrorMode flags */
#define fNoErrMes 1
#define fErrMes   0

/* Quiet Mode -- Note: EEL must be kept in sync with acmsetup.h */
typedef UINT EEL;       /* Exit Error Level */
#define eelSuccess            ((EEL)0x0000)
#define eelBootstrapperFailed ((EEL)0x0009) /* Used only in Bootstrapper! */

EEL  eelExitErrorLevel = eelBootstrapperFailed;
BOOL fQuietMode        = fFalse;
BOOL fExeced           = fFalse;
BOOL fWin31            = fFalse;

/* Forward Declarations */
VOID    CleanUpTempDir ( char *, char * );
BRC     BrcInstallFiles ( char *, char *, char * );
BOOL    FCreateTempDir ( char *, char * );
BRC     BrcCopyFiles ( char *, char *, char * );
VOID    RemoveFiles ( char * );
BRC     BrcCopy ( char *, char * );
LONG    LcbFreeDrive ( int );
BOOL    FVirCheck ( HANDLE );
HWND    HwndInitBootWnd ( HANDLE );
LRESULT CALLBACK BootWndProc ( HWND, UINT, WPARAM, LPARAM );
BOOL    FGetFileSize ( char *, UINT * );
BRC     BrcBuildFileLists ( char *, UINT );
VOID    FreeFileLists ( VOID );
BOOL    FExecAndWait ( char *, HWND );
BOOL    FWriteBatFile ( OFSTRUCT, char *, char * );
BOOL    FLstSectionExists ( char * szLstFileName, char * szSect );
DWORD   GetCpuArchitecture();
BOOL    FNotifyAcme ( VOID );
BOOL    FGetAcmeErrorLevel ( EEL * peel );
BOOL    FCreateRegKey      ( CSZC cszcKey );
BOOL    FDoesRegKeyExist   ( CSZC cszcKey );
BOOL    FCreateRegKeyValue ( CSZC cszcKey, CSZC cszcValue );
BOOL    FGetRegKeyValue    ( CSZC cszcKey, SZ szBuf, CB cbBufMax );
VOID    DeleteRegKey       ( CSZC cszcKey );
BOOL    FFlushRegKey ( VOID );
BOOL    FWriteToRestartFile ( SZ szTmpDir );
BOOL    FCreateIniFileName ( SZ szIniFile, CB cbBufMax );
BOOL    FReadIniFile ( SZ szIniFile, HLOCAL * phlocal, PCB pcbBuf );
BOOL    FAllocNewBuf ( CB cbOld, SZ szTmpDir, SZ szSection, SZ szKey,
                        HLOCAL * phlocal, PCB pcbToBuf );
BOOL    FProcessFile ( HLOCAL hlocalFrom, HLOCAL hlocalTo, CB cbToBuf,
                        SZ szTmpDir, SZ szSection, SZ szKey );
VOID    CopyIniLine ( SZ szKey, SZ szTmpDir, SZ szFile, PSZ pszToBuf );
BOOL    FWriteIniFile ( SZ szIniFile, HLOCAL hlocalTo );
BRC     BrcInsertDisk(CHAR *pchStf, CHAR *pchSrcDrive);
BOOL    FRenameBadMaintStf ( SZ szStf );


/* Bootstrapper list file params */
char    rgchSetupDirName[cchLstLineMax];
#ifdef UNUSED   /* Replaced by DrvWinClass */
char    rgchDrvModName[cchLstLineMax];
#endif  /* UNUSED */
char    rgchDrvWinClass[cchLstLineMax];
char    rgchCmdLine[cchLstLineMax];
char    rgchBootTitle[cchLstLineMax];
char    rgchBootMess[cchLstLineMax];
char    rgchWin31Mess[cchLstLineMax];
char    rgchCabinetFName[cchLstLineMax];
char    rgchBackgroundFName[cchLstLineMax];
char    rgchBkgWinClass[cchLstLineMax];
char    rgchInsertCDMsg[cchLstLineMax];
char    rgchInsertDiskMsg[cchLstLineMax];
LONG    lcbDiskFreeMin;
int     cFirstCabinetNum;
int     cLastCabinetNum;
HANDLE  hSrcLst = NULL;
HANDLE  hDstLst = NULL;
char    rgchErrorFile[cchFullPathMax];
HANDLE  hinstBoot = NULL;
HWND    hwndBoot = NULL;


CHAR rgchInsufMem[cchSzMax] = "";
CHAR rgchInitErr[cchSzMax]  = "";
CHAR rgchSetup[cchSzMax]    = "";


/*
**  'Fixup' temp dir string by removing any subdirs and ensuring
**  extension is only one character.  (Note - Win3.0 has bug with
**  WinExec'ing some EXEs from a full 8.3 directory!)
**************************************************************************/
void FixupTempDirName( LPSTR szDir )
{
    LPSTR   szNext;
    int     cch = 0;

    if (*szDir == '\\'
        || *(AnsiNext(szDir)) == ':')
        {
        lstrcpy(szDir, "~msstfqf.t");
        return;
        }

    while (*szDir != '\\'
        && *szDir != '.'
        && *szDir != '\0'
        && *szDir != ':'
        && cch++ < 8)
        {
        szDir = AnsiNext(szDir);
        }

    szNext = AnsiNext(szDir);
    if (*szDir == '.'
        && *szNext != '.'
        && *szNext != '\\'
        && *szNext != '\0'
        && *szNext != ':')
        {
        *(AnsiNext(szNext)) = '\0';
        return;
        }

    *szDir = '\0';
    lstrcat(szDir, ".t");
}


/* Displays bootstrapper messages.
 * If fError is true, it's an error message, otherwise it's
 * just a message (e.g. insert disk 1).
**************************************************************************/
int DispErrBrc ( BRC brc, BOOL fError, UINT fuStyle,
                    const char *sz1, const char *sz2,
                    const char *sz3 )
{
    char rgchTitle[256];
    char rgchMessage[256];
    char szBuf[256 + cchFullPathMax + 256];

#ifndef DEBUG
    if (fQuietMode)
        {
        return (IDCANCEL);
        }
#endif

    if (LoadString(hinstBoot, brcGen, rgchTitle, 256) == 0
        || LoadString(hinstBoot, brc, rgchMessage, 256) == 0)
        {
        MessageBox(hwndBoot, rgchInsufMem, rgchInitErr, MB_OK | MB_ICONSTOP);
        return 0;
        }
    
    if (!fError)
        lstrcpy(rgchTitle, rgchSetup);

    if (sz1 == NULL) sz1 = "";
    if (sz2 == NULL) sz2 = "";
    if (sz3 == NULL) sz3 = "";

    if (brc == brcFile)
        wsprintf(szBuf, rgchMessage, (LPSTR)AnsiUpper(rgchErrorFile));
    else if (brc == brcDS || brc == brcMemDS)
        wsprintf(szBuf, rgchMessage, lcbDiskFreeMin / 1024L);
    else
        wsprintf(szBuf, rgchMessage, sz1, sz2, sz3);

    if ((brc == brcMemDS || brc == brcNoSpill)
        && LoadString(hinstBoot, brcMemDSHlp, rgchMessage, 256))
        {
        lstrcat(szBuf, rgchMessage);
        }
    else if (brc == brcConnectToSource
        && LoadString(hinstBoot, brcConnectHlp, rgchMessage, 256))
        {
        lstrcat(szBuf, rgchMessage);
        }

    return (MessageBox(hwndBoot, szBuf, rgchTitle, fuStyle));
}


/*
**  Purpose:
**      Installs Setup executable in a temporary directory on an
**      available hardrive, and launches Setup.  After Setup
**      completes, removes the temporary files and directory.
**  Arguments:
**      Standard Windows WinMain args.
**  Returns:
**      Returns eelExitErrorLevel.  0 == Success.
**************************************************************************/
int WINAPI WinMain ( HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpszCmdParam, int nCmdShow )
{
    char    chDrive;
    char    rgchDstDir[cchFullPathMax] = " :\\";   // WARN: kept as OEM chars
    char    * szDstDirSlash = szNull;
    char    rgchModuleFileName[cchFullPathMax]; // WARN: kept as ANSI chars
    char    rgchLstFileName[cchFullPathMax];
    char    rgchTemp[cchFullPathMax];
    char    rgchSrcDir[cchFullPathMax];   
    UINT    cbLstSize;
    char    rgchWinExecLine[cchWinExecLineMax];
    UINT    uiRes;
    int     iModLen;
    BRC     brc;
    BOOL    fCleanupTemp = FALSE;
    LPSTR   sz;
    HWND    hWndBkg = 0;  /* window of background task */
    UINT    hMod;

    Unused(nCmdShow);
    hinstBoot = hInstance;

    rgchErrorFile[0] = '\0';
    
    if (LoadString(hinstBoot, IDS_InsufMem, rgchInsufMem,
            sizeof rgchInsufMem) == 0
        || LoadString(hinstBoot, IDS_InitErr, rgchInitErr,
            sizeof rgchInitErr) == 0
        || LoadString(hinstBoot, IDS_Setup, rgchSetup,
            sizeof rgchSetup) == 0)
        {
        /* REVIEW: If these LoadStrngs fail, the user will never know...
        *   But we can't hard-code strings in an .h file because INTL
        *   requires all localizable strings to be in resources!
        */
#ifdef DEBUG
        MessageBox(NULL, "Initial LoadString's failed; probably out of memory.",
                    szDebugMsg, MB_OK | MB_ICONEXCLAMATION);

#endif /* DEBUG */
        }
                                           
    for (sz = lpszCmdParam; *sz != '\0'; sz = AnsiNext(sz))
        {
        if ((*sz == '-' ||  *sz == '/')
                && toupper(*(sz+1)) == 'Q' && toupper(*(sz+2)) == 'T')
            {
            fQuietMode = fTrue;
            break;
            }
        }

/*
 * REVIEW: Check that this code is still functional before restoring it.
 */
#if VIRCHECK
    if (!FVirCheck(hInstance))
        {
        DispErrBrc(brcVir, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }
#endif

    if (hPrevInstance || FindWindow(szBootClass, NULL) != NULL)
        {
        DispErrBrc(brcInst, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }

    GetModuleFileName(hInstance, rgchModuleFileName, cchFullPathMax);

    /*
     * If the first switch on the command line is /M, then it specifies
     * the real module name to use.
     */
    if ((lpszCmdParam[0] == '-' || lpszCmdParam[0] == '/')
            && toupper(lpszCmdParam[1]) == 'M')
        {
        char *pCh, *pCh2;
        BOOL fQuotedFileName;
        
        /* Skip the spaces */
        for (pCh = lpszCmdParam+2; *pCh == ' '; pCh++);
        fQuotedFileName = (*pCh == '\"');
        if (fQuotedFileName)
            {
            pCh++;
            }
        
        /* Copy the file name, and add the EOS */
        lstrcpy(rgchModuleFileName, pCh);
        for (pCh2=rgchModuleFileName; 
            (*pCh2 != ' ' || fQuotedFileName) && 
            (*pCh2 != '\"' || !fQuotedFileName) && 
             *pCh2 != '\0'; 
            pCh2++);
        *pCh2 = '\0';
        
        /* Remove the /M param from the command line */
        lpszCmdParam = pCh + lstrlen(rgchModuleFileName);
        if (fQuotedFileName && *lpszCmdParam == '\"')
            {
            lpszCmdParam++;
            }

        /* Remove trailing whitespace from the command line */
        for (pCh = lpszCmdParam; *pCh == ' '; pCh++);
        lpszCmdParam = pCh;
        }


    OemToAnsi(rgchModuleFileName, rgchModuleFileName);

    // Windows 3.0 bug with UNC paths - prepends windows drive letter
    sz = (LPSTR)rgchModuleFileName;
    if (*sz != '\0'
        && *sz != '\\'
        && *(sz = AnsiNext(sz)) == ':'
        && *(sz = AnsiNext(sz)) == '\\'
        && *AnsiNext(sz) == '\\')
        {
        LPSTR szDst = (LPSTR)rgchModuleFileName;

        while ((*szDst++ = *sz++) != '\0')
            ;
        }

    iModLen = lstrlen(rgchModuleFileName);
    lstrcpy(rgchSrcDir, rgchModuleFileName);
    sz = (LPSTR)&rgchSrcDir[iModLen];
    while (sz > (LPSTR)rgchSrcDir && *sz != '\\')
        sz = AnsiPrev(rgchSrcDir, sz);
    Assert(sz > (LPSTR)rgchSrcDir);
    *(AnsiNext(sz)) = '\0';               

    /*
     * If the first switch on the command line is /L, then it specifies
     * the name of the .lst file to use.
     */
    rgchTemp[0] = '\0';
    if ((lpszCmdParam[0] == '-' || lpszCmdParam[0] == '/')
            && toupper(lpszCmdParam[1]) == 'L')
        {
        char *pCh, *pCh2;
        
        /* Skip the spaces */
        for (pCh = lpszCmdParam+2; *pCh == ' ' && *pCh != '\0'; pCh++);
        
        /* Copy the .lst file name, and add the newline */
        lstrcpy(rgchTemp, pCh);
        for (pCh2=rgchTemp; *pCh2 != ' ' && *pCh2!= '\0'; pCh2++);
        *pCh2 = '\0';
        
        /* Remove the /L param from the command line */
        lpszCmdParam = pCh + lstrlen(rgchTemp);
        for (pCh = lpszCmdParam; *pCh == ' ' && *pCh != '\0'; pCh++);
        lpszCmdParam = pCh;
        }


    /* If there is something on the command line, use it as the .lst file */
    if (*rgchTemp != '\0')
        {
        lstrcpy(rgchLstFileName, rgchSrcDir);
        lstrcat(rgchLstFileName, rgchTemp);
        }
    else
        {
        lstrcpy(rgchLstFileName, rgchModuleFileName);
        sz = (LPSTR)&rgchLstFileName[iModLen];
        while (sz > (LPSTR)rgchLstFileName && *sz != '.')
            sz = AnsiPrev(rgchLstFileName, sz);
        Assert(sz > (LPSTR)rgchLstFileName);
        *(AnsiNext(sz)) = '\0';
        lstrcat(rgchLstFileName, szLstExt);
        }

    if (!FGetFileSize(rgchLstFileName, &cbLstSize) || cbLstSize == 0)
        {
        lstrcpy(rgchErrorFile, rgchLstFileName);
        DispErrBrc(brcFile, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }

#ifndef WF_WINNT
#define WF_WINNT 0x4000
#endif

    /* Attempt to use appropriate platform.
    */
    szParamsSect = szNull;
    szFilesSect  = szNull;

    if (1)
        {
        DWORD dwVers = 0;
        DWORD dwCpuArchitecture;
        DWORD dwMajorVersion;
        DWORD dwMinorVersion;
        PPLATFORM_SPEC pPlatformSpec;

        dwCpuArchitecture = GetCpuArchitecture();

        dwVers = GetVersion();
        dwMajorVersion = LOBYTE(LOWORD(dwVers));
        dwMinorVersion = HIBYTE(LOWORD(dwVers));

        if (dwCpuArchitecture < (sizeof (aaPlatformSpecs) / sizeof(aaPlatformSpecs[0])))
            {
            pPlatformSpec = aaPlatformSpecs[dwCpuArchitecture];
            } 
        else 
            {
            pPlatformSpec = aEmptySpec;
            }

        for (; pPlatformSpec->szParamsSect != NULL; pPlatformSpec++)
            {
            if (((pPlatformSpec->minMajorVersion < dwMajorVersion) || 
                 (pPlatformSpec->minMajorVersion == dwMajorVersion && pPlatformSpec->minMinorVersion <= dwMinorVersion)) &&
                FLstSectionExists(rgchLstFileName, pPlatformSpec->szParamsSect))
                {
                szParamsSect = pPlatformSpec->szParamsSect;
                szFilesSect  = pPlatformSpec->szFilesSect;
                break;
                }
            }
        }
    else    /* non-WinNT */
        {
        if (FLstSectionExists(rgchLstFileName, szWin95ParamsSect)
            && (LOBYTE(LOWORD(GetVersion())) > 3
                || HIBYTE(LOWORD(GetVersion())) >= 95))
            {
            szParamsSect = szWin95ParamsSect;
            szFilesSect  = szWin95FilesSect;
            }
        else
            {
            fWin31 = fTrue;
            if (FLstSectionExists(rgchLstFileName, szWin3xParamsSect))
                {
                szParamsSect = szWin3xParamsSect;
                szFilesSect  = szWin3xFilesSect;
                }
            }
        }

    if (szParamsSect == szNull)
        {
        if (FLstSectionExists(rgchLstFileName, szDefaultParamsSect))
            {
            szParamsSect = szDefaultParamsSect;
            szFilesSect  = szDefaultFilesSect;
            }
        else
            {
            DispErrBrc(brcNoCpuSect, TRUE, MB_OK | MB_ICONSTOP, NULL,
                    NULL, NULL);
            goto LCleanupAndExit;
            }
        }

    if (GetPrivateProfileString(szParamsSect, "TmpDirName", "",
                rgchSetupDirName, cchLstLineMax, rgchLstFileName) <= 0
        || (lcbDiskFreeMin = GetPrivateProfileInt(szParamsSect,
                "TmpDirSize", 0, rgchLstFileName) * 1024L) <= 0
        || (cFirstCabinetNum = GetPrivateProfileInt(szParamsSect,
                "FirstCabNum", 1, rgchLstFileName)) <= 0
        || (cLastCabinetNum = GetPrivateProfileInt(szParamsSect,
                "LastCabNum", 1, rgchLstFileName)) <= 0
#ifdef UNUSED
        || GetPrivateProfileString(szParamsSect, "DrvModName", "",
                rgchDrvModName, cchLstLineMax, rgchLstFileName) <= 0
#endif  /* UNUSED */
        || GetPrivateProfileString(szParamsSect, "DrvWinClass", "",
                rgchDrvWinClass, cchLstLineMax, rgchLstFileName) <= 0
        || GetPrivateProfileString(szParamsSect, "CmdLine", "", rgchCmdLine,
                cchLstLineMax, rgchLstFileName) <= 0
	//|| GetPrivateProfileString(szParamsSect, "Require31", "",
	//	rgchWin31Mess, cchLstLineMax, rgchLstFileName) <= 0
        || GetPrivateProfileString(szParamsSect, "WndTitle", "",
                rgchBootTitle, cchLstLineMax, rgchLstFileName) <= 0
        || GetPrivateProfileString(szParamsSect, "WndMess", "",
                rgchBootMess, cchLstLineMax, rgchLstFileName) <= 0)
        {
        DispErrBrc(brcLst, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }

    if (FindWindow(rgchDrvWinClass, NULL) != NULL)
        {
        DispErrBrc(brcInst, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }

    GetPrivateProfileString(szParamsSect, "CabinetFile", "",
            rgchCabinetFName, cchLstLineMax, rgchLstFileName);

    GetPrivateProfileString(szParamsSect, "InsertCDMsg", "",
            rgchInsertCDMsg, cchLstLineMax, rgchLstFileName);

    GetPrivateProfileString(szParamsSect, "InsertDiskMsg", "",
            rgchInsertDiskMsg, cchLstLineMax, rgchLstFileName);

    if (rgchWin31Mess[0] != '\0'
        && LOBYTE(LOWORD((DWORD)GetVersion())) == 3
        && HIBYTE(LOWORD((DWORD)GetVersion())) < 10)
        {
        if (!fQuietMode)
            {
            char rgchTitle[256];
    
            if (LoadString(hinstBoot, brcGen, rgchTitle, 256) == 0)
                lstrcpy(rgchTitle, rgchSetup);
            MessageBox(hwndBoot, rgchWin31Mess, rgchTitle,
                        MB_OK | MB_ICONSTOP);
            }
        goto LCleanupAndExit;
        }

    FixupTempDirName(rgchSetupDirName);

    for (sz = rgchBootMess; *sz != '\0'; sz = AnsiNext(sz))
        if (*sz == '\\' && *(sz+1) == 'n')
            {
            *sz++ = '\r';
            *sz   = '\n';
            }

    /* If there is a /W then is specifies we are in add/remove mode with
       the setup app not installed. We need to read it off CD/Floppy/Network
      */
    if ((lpszCmdParam[0] == '-' || lpszCmdParam[0] == '/')
            && toupper(lpszCmdParam[1]) == 'W')
        {
        CHAR    rgchStf[_MAX_PATH];
        char *pCh, *pCh2, *pCh3;
        
        /* Skip the spaces */
        for (pCh = lpszCmdParam+2; *pCh == ' ' && *pCh != '\0'; pCh++);
        
        lstrcpy(rgchStf, rgchSrcDir);
        pCh3 = rgchStf + lstrlen(rgchStf);
        /* Copy the .stf file name, and add the newline */
        for (pCh2=pCh; *pCh2 != ' ' && *pCh2!= '\0'; pCh2++)
            *pCh3++ = *pCh2;
        *pCh3 = '\0';
    
        /* Remove the /W parameter */
        lpszCmdParam = pCh2;

        /* Get them to insert the correct disk */
        if ((brc = BrcInsertDisk(rgchStf, rgchSrcDir)) != brcOkay)
            {
            if (brc != brcMax)
                DispErrBrc(brc, TRUE, MB_OK | MB_ICONSTOP, rgchStf, NULL, NULL);
            goto LCleanupAndExit;
            }
        }

    GetPrivateProfileString(szParamsSect, "Background", "",
            rgchBackgroundFName, cchLstLineMax, rgchLstFileName);
    GetPrivateProfileString(szParamsSect, "BkgWinClass", "",
            rgchBkgWinClass, cchLstLineMax, rgchLstFileName);
    if (rgchBackgroundFName[0] != '\0')
        {
        lstrcpy(rgchTemp, rgchSrcDir);
        lstrcat(rgchTemp, rgchBackgroundFName);
        if (rgchBkgWinClass[0] != '\0')
            {
            lstrcat(rgchTemp, " /C");
            lstrcat(rgchTemp, rgchBkgWinClass);
            }
        lstrcat(rgchTemp, " /T");
        lstrcat(rgchTemp, rgchBootTitle);
        lstrcat(rgchTemp, " /M");
        lstrcat(rgchTemp, rgchBootMess);

        hMod = WinExec(rgchTemp, SW_SHOWNORMAL);  /* ignore if exec failed */
#ifdef DEBUG
        if (hMod < 32)
            {
            wsprintf(szDebugBuf, "%s: Background WinExec failed.",
                        rgchBackgroundFName);
            MessageBox(NULL, szDebugBuf, szDebugMsg, MB_OK | MB_ICONSTOP);
            }
#endif  /* DEBUG */

        hWndBkg = FindWindow(rgchBkgWinClass, rgchBootTitle);
        }
    if (!hWndBkg && (hwndBoot = HwndInitBootWnd(hInstance)) == NULL)
            {
            DispErrBrc(brcMem, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
            goto LCleanupAndExit;
            }

    if ((brc = BrcBuildFileLists(rgchLstFileName, cbLstSize)) != brcOkay)
        {
        DispErrBrc(brc, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }
    
    lstrcat(rgchDstDir, "~MSSETUP.T");
    szDstDirSlash = rgchDstDir + lstrlen(rgchDstDir);
    lstrcat(rgchDstDir, "\\");

    lstrcat(rgchDstDir, rgchSetupDirName);
    AnsiToOem(rgchDstDir, rgchDstDir);
    for (chDrive = 'Z'; chDrive >= 'A'; --chDrive)
        {
        UINT fModeSav;
        BOOL fDriveFixed;

        fModeSav = SetErrorMode(fNoErrMes);
        fDriveFixed = (GetDriveTypeEx(chDrive - 'A') == EX_DRIVE_FIXED);
        SetErrorMode(fModeSav);
        if (fDriveFixed)
            {
            *rgchDstDir = chDrive;
            brc = BrcInstallFiles(rgchSrcDir, rgchDstDir, szDstDirSlash);
            if (brc == brcOkay)
                break;
            if (brc == brcFile)
                {
                DispErrBrc(brc, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
                goto LCleanupAndExit;
                }
            else if (brc == brcNoSpill)
                {
                /* Message already handled in HfOpenSpillFile */
                goto LCleanupAndExit;
                }
            }
        }

    if (chDrive < 'A')
        {
        uiRes = GetWindowsDirectory(rgchDstDir, cchFullPathMax);
        Assert(uiRes > 0);
#if DBCS    // [J1] Fixed DBCS raid #46.
        AnsiUpper(rgchDstDir);
#endif
        /* BLOCK */
            {
            LPSTR sz = (LPSTR)&rgchDstDir[uiRes];

            sz = AnsiPrev(rgchDstDir, sz);
            if (*sz != '\\')
                lstrcat(rgchDstDir, "\\");
            }

        lstrcat(rgchDstDir, "~MSSETUP.T");
        szDstDirSlash = rgchDstDir + lstrlen(rgchDstDir);
        lstrcat(rgchDstDir, "\\");

        Assert(lstrlen(rgchDstDir) + lstrlen(rgchSetupDirName)
                < cchFullPathMax);
        lstrcat(rgchDstDir, rgchSetupDirName);
        AnsiToOem(rgchDstDir, rgchDstDir);
        brc = BrcInstallFiles(rgchSrcDir, rgchDstDir, szDstDirSlash);
        if (brc != brcOkay)
            {
            /* NoSpill message already handled in HfOpenSpillFile */
            if (brc != brcNoSpill)
                {
                DispErrBrc(brc, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
                }
            goto LCleanupAndExit;
            }
        }

    hSrcLst = LocalFree(hSrcLst);   /* don't need src list anymore */
    Assert(hSrcLst == NULL);

    /* Use full path to .exe; don't rely on cwd (fails under Win95).
    */
    /* block */
        {
        char rgchTmp[cchWinExecLineMax];

        wsprintf(rgchTmp, rgchCmdLine, (LPSTR)rgchSrcDir,
                lpszCmdParam);

        Assert(lstrlen(rgchTmp) + lstrlen(rgchDstDir) + 1 < cchWinExecLineMax);

        lstrcpy(rgchWinExecLine, rgchDstDir);
        lstrcat(rgchWinExecLine, "\\");
        lstrcat(rgchWinExecLine, rgchTmp);
        }
    GlobalCompact((DWORD)(64L * 1024L));
    fCleanupTemp = TRUE;

    if (!fWin31 && !FNotifyAcme())
        {
#if DEBUG
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONEXCLAMATION, NULL, NULL, NULL);
#endif /* DEBUG */
        /* Try running Acme anyway. */
        }
    if (!fWin31 && !FWriteToRestartFile(rgchDstDir))
        {
#ifdef DEBUG
        MessageBox(NULL, "Write to restart file failed. Setup can continue, "
                    "but some initialization files might not get removed "
                    "if Setup must restart Windows.",
                    szDebugMsg, MB_OK | MB_ICONEXCLAMATION);

#endif /* DEBUG */
        /*
         *  Any errors encountered will have been displayed where they occured.
         *  Try running Acme anyway.
         */
        }
    if (hWndBkg)
        SendMessage(hWndBkg, WM_COMMAND, IDM_ACME_STARTING, 0);

    if (!FExecAndWait(rgchWinExecLine, hwndBoot))
        {
        DispErrBrc(brcMem, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        goto LCleanupAndExit;
        }
    fExeced = fTrue;

LCleanupAndExit:
    if (fCleanupTemp && szDstDirSlash != szNull)
        CleanUpTempDir(rgchDstDir, szDstDirSlash);
    FreeFileLists();
    eelExitErrorLevel = eelBootstrapperFailed;
    if (fExeced && !FGetAcmeErrorLevel(&eelExitErrorLevel))
        {
#ifdef UNUSED
        /* NOTE: Removed to avoid the message on WinNT.  On NT, Acme can
        *   exit and the bootstrapper can kick in before the restart
        *   actually happens, causing this message (since Acme has already
        *   removed the reg key as part of its reboot cleanup).  We'll
        *   leave the eelFailed value, though, since no one should be
        *   relying on it at reboot anyway, and it may help catch other
        *   problems down the road.
        */
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
#endif /* UNUSED */
        Assert(eelExitErrorLevel == eelBootstrapperFailed);
        }
    if (hwndBoot != NULL)
        DestroyWindow(hwndBoot);
    if (hWndBkg && IsWindow(hWndBkg))
        {
        SendMessage(hWndBkg, WM_COMMAND, eelExitErrorLevel == eelSuccess ?
                    IDM_ACME_COMPLETE : IDM_ACME_FAILURE, 0);
        if (IsWindow(hWndBkg))
            PostMessage(hWndBkg, WM_QUIT, 0, 0);
        }
    return (eelExitErrorLevel);
}


/*
**  Purpose:
**      Creates and temporary subdirectory at the given path,
**      appends it to the given path, and copies the Setup files
**      into it.
**  Arguments:
**      szModule: Full path to bootstrapper's directory (ANSI chars).
**      rgchDstDir: Full path to destination directory (OEM chars).
**  Returns:
**      One of the following Bootstrapper return codes:
**          brcMem    out of memory
**          brcDS     out of disk space
**          brcMemDS  out of memory or disk space
**          brcFile   expected source file missing
**          brcOkay   completed without error
**************************************************************************/
BRC BrcInstallFiles ( char * szModule, char * rgchDstDir,
                    char * szDstDirSlash )
{
    BRC brc;

    if (LcbFreeDrive(*rgchDstDir - 'A' + 1) < lcbDiskFreeMin)
        return (brcDS);
    if (!FCreateTempDir(rgchDstDir, szDstDirSlash))
        return (brcMemDS);
    if ((brc = BrcCopyFiles(szModule, rgchDstDir, szDstDirSlash)) != brcOkay)
        {
        CleanUpTempDir(rgchDstDir, szDstDirSlash);
        return (brc);
        }

    SetFileAttributes(rgchDstDir, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);

    Assert(szDstDirSlash);
    Assert(*szDstDirSlash == '\\');
    *szDstDirSlash = '\0';
    SetFileAttributes(rgchDstDir, FILE_ATTRIBUTE_HIDDEN | FILE_ATTRIBUTE_READONLY);
    *szDstDirSlash = '\\';

    return (brcOkay);
}


/*
**  Purpose:
**      Removes the temporary files and directory.
**  Arguments:
**      rgchDstDir: Full path to temp directory (OEM chars).
**  Returns:
**      None.
**************************************************************************/
VOID CleanUpTempDir ( char * rgchDstDir, char * szDstDirSlash )
{
    char rgchRoot[] = " :\\";
    int i;

    RemoveFiles(rgchDstDir);

    rgchRoot[0] = *rgchDstDir;
    _chdir(rgchRoot);

    SetFileAttributes(rgchDstDir, FILE_ATTRIBUTE_NORMAL);

    /* Try to remove the directory up to cRetryMax times.
    */
    for (i = 0; i < cRetryMax; i++)
        {
        if (_rmdir(rgchDstDir) == 0)
            break;
        }

    Assert(szDstDirSlash);
    Assert(*szDstDirSlash == '\\');
    *szDstDirSlash = '\0';
    SetFileAttributes(rgchDstDir, FILE_ATTRIBUTE_NORMAL);

    /* Try to remove the directory up to cRetryMax times.
    */
    for (i = 0; i < cRetryMax; i++)
        {
        if (_rmdir(rgchDstDir) == 0)
            break;
        }

    *szDstDirSlash = '\\';
}


/*
**  Purpose:
**      Creates a temporary subdirectory at the given path,
**      and appends it to the given path.
**  Arguments:
**      rgchDir: Full path to destination directory (OEM chars).
**  Returns:
**      TRUE if directory was successfully created,
**      FALSE if not.
**************************************************************************/
BOOL FCreateTempDir ( char * rgchDir, char * szDstDirSlash )
{
    char    rgchTmp[cchFullPathMax];
    FILE *  fp;
    char *  pch;
    int     fErr;
    int     i = 0;

    pch = (char *)(&rgchDir[lstrlen(rgchDir)]);
    Assert(*pch == '\0');
    _chdrive(*rgchDir - 'A' + 1);

    Assert(szDstDirSlash);
    Assert(*szDstDirSlash == '\\');
    *szDstDirSlash = '\0';
    _mkdir(rgchDir);
    *szDstDirSlash = '\\';

    while (!_access(rgchDir, 0))
        {
        if (!_chdir(rgchDir))
            {
            /* verify dir is write-able */
            lstrcpy(rgchTmp, rgchDir);
            lstrcat(rgchTmp, "\\tXXXXXX");
            Assert(lstrlen(rgchTmp) < cchFullPathMax);
            if (_mktemp(rgchTmp) != NULL
                && (fp = fopen(rgchTmp, "w")) != NULL)
                {
                fErr = fclose(fp);
                Assert(!fErr);

                fErr = remove(rgchTmp);
#ifdef DBCS     // [J2] Fixed DBCS raid #28.
                if (fErr)       // Keep the directory name
                    *pch = '\0';
#else
                *pch = '\0';
#endif
                return (!fErr);
                }
            }
        if (++i > 9)
            break;
        _itoa(i, pch, 10);
        Assert(lstrlen(rgchDir) < cchFullPathMax);
        }

    if (i <= 9 && !_mkdir(rgchDir))
        {
        fErr = _chdir(rgchDir);
        Assert(!fErr);
#ifdef DBCS     // [J2] Fixed DBCS raid #28.
//      Keep the directory name
#else
        *pch = '\0';
#endif
        return (TRUE);
        }

    *pch = '\0';

    return (FALSE);
}

/*
**  Purpose:
**      Reopens BAT file and writes DEL or RMDIR line.
**  Arguments:
**      of:    OFSTRUCT to REOPEN.
**      szCmd: Command (ANSI chars).  ["DEL" or "RMDIR"]
**      szArg: Fully qualified pathname for argument (OEM chars).
**  Returns:
**      TRUE or FALSE.
**************************************************************************/
BOOL FWriteBatFile ( OFSTRUCT of, char * szCmd, char * szArg )
{
    int     fhBat = -1;
    BOOL    fRet = TRUE;

    if ((fhBat = OpenFile("a", &of, OF_REOPEN | OF_WRITE)) == -1
        || _llseek(fhBat, 0L, 2) == -1L
        || _lwrite(fhBat, szCmd, lstrlen(szCmd)) != (UINT)lstrlen(szCmd)
        || _lwrite(fhBat, (LPSTR)" ", 1) != 1
        || _lwrite(fhBat, szArg, lstrlen(szArg)) != (UINT)lstrlen(szArg)
        || _lwrite(fhBat, (LPSTR)"\r\n", 2) != 2)
        {
        fRet = FALSE;
        }

    if (fhBat != -1)
        {
        int fErr = _lclose(fhBat);

        Assert(fErr != -1);
        }

    return (fRet);
}

#ifdef DEBUG
/*
**  Purpose:
**      Checks if destination filename is a valid 8.3 name with no path
*/
BOOL FValidFATFileName ( char* szName )
{
    int  iLen, ch;
    for (iLen = 0; (ch = *szName++) != '\0'; iLen++)
        {
        if (ch <= ' ' || ch == '\\' || ch == ':' || ch == ',')
            return fFalse;
        if (ch == '.')
            {
            if (iLen == 0 || iLen > 8)
                return fFalse;
            iLen = 9;
            }
        if (iLen == 8 || iLen == 13)
            return fFalse;
        }
    return (iLen > 0);
}
#endif  /* DEBUG */


/*
**  Purpose:
**      Copies the source files into the given destination dir.
**  Arguments:
**      szModule: Source path (ANSI chars).
**      szDstDir: Destination path (OEM chars).
**  Returns:
**      One of the following bootstrapper return codes:
**          brcMem    out of memory
**          brcDS     out of disk space
**          brcMemDS  out of memory or disk space
**          brcFile   expected source file missing
**          brcOkay   completed without error
**************************************************************************/
BRC BrcCopyFiles ( char * szModule, char * szDstDir, char * szDstDirSlash )
{
    char        rgchSrcFullPath[cchFullPathMax];
    char        rgchDstFullPath[cchFullPathMax];
    char        rgchTmpDirPath[cchFullPathMax];
    char *      szSrc;
    char *      szDst;
    int         cbSrc;
    BRC         brc = brcOkay;
    int         fhBat = -1;
    OFSTRUCT    ofBat;
    int         fErr;
    BOOL        fCabinetFiles = FALSE;

    lstrcpy(rgchDstFullPath, szDstDir);
    lstrcat(rgchDstFullPath, "\\_MSSETUP._Q_");
    Assert(lstrlen(rgchDstFullPath) < cchFullPathMax);
    _chmod(rgchDstFullPath, S_IREAD | S_IWRITE);
    remove(rgchDstFullPath);

    OemToAnsi(rgchDstFullPath, rgchDstFullPath);
    fhBat = OpenFile(rgchDstFullPath, &ofBat, OF_CREATE | OF_WRITE);
    AnsiToOem(rgchDstFullPath, rgchDstFullPath);
    if (fhBat == -1)
        return (brcMemDS);

    fErr = _lclose(fhBat);
    Assert(!fErr);

    szSrc = (char *)LocalLock(hSrcLst);
    if (szSrc == NULL)
         return (brcMem);

    szDst = (char *)LocalLock(hDstLst);
    
    if (szDst == NULL) {
        LocalUnlock (hSrcLst);
        return (brcMem);
    }

    for (;
        (cbSrc = lstrlen(szSrc)) != 0;
        szSrc += cbSrc + 1, szDst += lstrlen(szDst) + 1)
        {
        
        //
        //  This code has been added so that we can detect a path
        //  in setup.lst for the right hand side of the equals sign.  This
        //  allows us flexiblity in specifying where files like setup.inf
        //  should be pulled from, otherwise we always use the files from
        //  the original source location.  If we detect "<anything>:\" or
        //  "\\" then we assume it is a path.
        //
        if( ((':' == szSrc[1]) && ('\\' == szSrc[2])) ||
            (('\\' == szSrc[0]) && ('\\' == szSrc[1])) )
        {
            rgchSrcFullPath[0] = '\0';
        }
        else
        {
            lstrcpy(rgchSrcFullPath, szModule);
        }

        lstrcat(rgchSrcFullPath, szSrc);
        lstrcpy(rgchDstFullPath, szDstDir);
        lstrcat(rgchDstFullPath, "\\");
        lstrcat(rgchDstFullPath, szDst);
#ifdef DEBUG
        if (!FValidFATFileName(szDst))
            {
            wsprintf(szDebugBuf, "Invalid destination file, must be 8.3: %s",
                        szDst);
            MessageBox(NULL, szDebugBuf, szDebugMsg, MB_OK | MB_ICONSTOP);
            continue;
            }
#endif  /* DEBUG */
        Assert(lstrlen(rgchSrcFullPath) < cchFullPathMax);
        Assert(lstrlen(rgchDstFullPath) < cchFullPathMax);
        if (   !FWriteBatFile(ofBat, "ATTRIB -R", rgchDstFullPath)
            || !FWriteBatFile(ofBat, "DEL",       rgchDstFullPath))
            {
            brc = brcDS;
            break;
            }

        if (*szSrc == '@')  /* cabinet file */
            {
            if (*rgchCabinetFName == '\0')
                {
                brc = brcFile;
#ifdef DEBUG
                lstrcpy(rgchErrorFile, ". Missing CABINET= line");
#endif //DEBUG
                break;
                }
            fCabinetFiles = TRUE;
            continue;
            }

        if ((brc = BrcCopy(rgchSrcFullPath, rgchDstFullPath)) != brcOkay)
            break;
        _chmod(rgchDstFullPath, S_IREAD);
        }
    LocalUnlock(hSrcLst);
    LocalUnlock(hDstLst);

    lstrcpy(rgchDstFullPath, szDstDir);
    lstrcat(rgchDstFullPath, "\\_MSSETUP._Q_");
    Assert(lstrlen(rgchDstFullPath) < cchFullPathMax);

    Assert(szDstDirSlash != szNull);
    Assert(*szDstDirSlash == chDirSep);
    *szDstDirSlash = chEos;
    lstrcpy(rgchTmpDirPath, szDstDir);
    *szDstDirSlash = chDirSep;

    if (brc == brcOkay
        && (!FWriteBatFile(ofBat, "DEL", rgchDstFullPath)
        || !FWriteBatFile(ofBat, "RMDIR", szDstDir)
        || !FWriteBatFile(ofBat, "RMDIR", rgchTmpDirPath)))
        {
        return (brcDS);
        }

    if (fCabinetFiles && brc == brcOkay)
        {
        szSrc = (char *)LocalLock(hSrcLst);
        if(szSrc == NULL)
            return (brcMem);

        szDst = (char *)LocalLock(hDstLst);
        if( szDst == NULL) {
            LocalUnlock (hSrcLst);
            return (brcMem);
        }
#ifdef DEBUG
        if (!FValidFATFileName(rgchCabinetFName))
            {
            wsprintf(szDebugBuf, "Invalid cabinet file, must be 8.3: %s",
                            rgchCabinetFName);
            MessageBox(NULL, szDebugBuf, szDebugMsg, MB_OK | MB_ICONSTOP);
            }
        else            
#endif  /* DEBUG */

        brc = BrcHandleCabinetFiles(hwndBoot, rgchCabinetFName,
                    cFirstCabinetNum, cLastCabinetNum, szModule, szDstDir,
                    szSrc, szDst, rgchErrorFile, rgchDstFullPath);

        LocalUnlock(hSrcLst);
        LocalUnlock(hDstLst);
        }

    return (brc);
}


/*
**  Purpose:
**      Removes the files previously copied to the temp dest dir.
**  Arguments:
**      szDstDir: full path to destination directory (OEM chars).
**  Returns:
**      None.
**************************************************************************/
VOID RemoveFiles ( char * szDstDir )
{
    char    rgchDstFullPath[cchFullPathMax];
    char *  szDst;
    int     cbDst;
    int     i;
    OFSTRUCT ofs;
    UINT    fModeSav;

    fModeSav = SetErrorMode(fNoErrMes);
    szDst = (char *)LocalLock(hDstLst);
    
    if (szDst == NULL)
        return;

    for (; (cbDst = lstrlen(szDst)) != 0; szDst += cbDst + 1)
        {
        lstrcpy(rgchDstFullPath, szDstDir);
        lstrcat(rgchDstFullPath, "\\");
        lstrcat(rgchDstFullPath, szDst);
        Assert(lstrlen(rgchDstFullPath) < cchFullPathMax);
        
        /* Don't try to remove the file if it doesn't exist */
        if (OpenFile(rgchDstFullPath, &ofs, OF_EXIST) == HFILE_ERROR)
            continue;

        /* Try to _chmod the file up to cRetryMax times.
        */
        for (i = 0; i < cRetryMax; i++)
            {
            if (_chmod(rgchDstFullPath, S_IWRITE) == 0)
                break;
            FYield();
            }

        /* Try to remove the file up to cRetryMax times.
        */
        for (i = 0; i < cRetryMax; i++)
            {
            if (remove(rgchDstFullPath) == 0)
                break;
            FYield();
            }
        }

    LocalUnlock(hDstLst);
    SetErrorMode(fModeSav);

    lstrcpy(rgchDstFullPath, szDstDir);
    lstrcat(rgchDstFullPath, "\\_MSSETUP._Q_");
    Assert(lstrlen(rgchDstFullPath) < cchFullPathMax);
    _chmod(rgchDstFullPath, S_IWRITE);
    remove(rgchDstFullPath);
}


/*
**  Purpose:
**      Copies the given source file to the given destination.
**  Arguments:
**      szFullPathSrc: full path name of source file (ANSI chars).
**      szFullPathDst: full path name of destination file (OEM chars).
**  Returns:
**      One of the following bootstrapper return codes:
**          brcMem    out of memory
**          brcDS     out of disk space
**          brcMemDS  out of memory or disk space
**          brcFile   expected source file missing
**          brcOkay   completed without error
**************************************************************************/
BRC BrcCopy ( char * szFullPathSrc, char * szFullPathDst )
{
    int         fhSrc = -1;
    int         fhDst = -1;
    OFSTRUCT    ofSrc, ofDst;
    BRC         brc = brcMemDS;
    int         fErr;

#ifdef APPCOMP
    if ((fhSrc = OpenFile(szFullPathSrc, &ofSrc, OF_READ)) == -1)
        {
        brc = brcFile;
        lstrcpy(rgchErrorFile, szFullPathSrc);
        goto CopyFailed;
        }
#endif /* APPCOMP */

    /* REVIEW: BUG: if szFullPathDst is an existing subdirectory
    ** instead of a file, we'll fail trying to open it, think we're
    ** out of disk space, and go back up to try another disk.
    ** This is acceptable for now.
    */
    _chmod(szFullPathDst, S_IREAD | S_IWRITE);
    OemToAnsi(szFullPathDst, szFullPathDst);
    fhDst = OpenFile(szFullPathDst, &ofDst, OF_CREATE | OF_WRITE);
    AnsiToOem(szFullPathDst, szFullPathDst);
    if (fhDst == -1)
        goto CopyFailed;

#ifdef APPCOMP
    if (WReadHeaderInfo(fhSrc) > 0)
        {
        LONG lRet;

        lRet = LcbDecompFile(fhSrc, fhDst, -1, 0, TRUE, NULL, 0L, NULL, 0,
                NULL);
        if (lRet < 0L)
            {
            if (lRet == (LONG)rcOutOfMemory)
                brc = brcMem;
            if (lRet == (LONG)rcWriteError)
                brc = brcDS;
            goto CopyFailed;
            }
        FFreeHeaderInfo();
        }
    else    /* copy the file using LZExpand */
#endif /* APPCOMP */
        {
        HFILE   hSrcLZ;
        DWORD   dwRet;

#ifdef APPCOMP
        fErr = _lclose(fhSrc);
        Assert(!fErr);
        fhSrc = -1;
#endif /* APPCOMP */

        if ((hSrcLZ = LZOpenFile(szFullPathSrc, &ofSrc, OF_READ)) == -1)
            {
            brc = brcFile;
            lstrcpy(rgchErrorFile, szFullPathSrc);
            goto CopyFailed;
            }

        /* We would like to yield more often, but LZCopy has no callbacks */
        FYield();
        
        dwRet = LZCopy(hSrcLZ, fhDst);
        LZClose(hSrcLZ);

        if (dwRet >= LZERROR_UNKNOWNALG)
            {
            if (dwRet == LZERROR_GLOBALLOC)
                brc = brcMem;
            if (dwRet == LZERROR_WRITE)
                brc = brcDS;
            goto CopyFailed;
            }
        }

    brc = brcOkay;

CopyFailed:
#ifdef APPCOMP
    if (fhSrc != -1)
        {
        fErr = _lclose(fhSrc);
        Assert(!fErr);
        }
#endif /* APPCOMP */
    if (fhDst != -1)
        {
        fErr = _lclose(fhDst);
        Assert(!fErr);
        }

    return (brc);
}


/*
**  Purpose:
**      Determine the storage space remaining on disk.
**  Arguments:
**      nDrive: drive number (1='A', 2='B', etc.)
**  Returns:
**      Number of bytes free on disk,
**      or 0 if not a valid drive.
+++
**  Implementation:
**      Calls DOS interrupt 21h, funct 36h.
**************************************************************************/
LONG LcbFreeDrive ( int nDrive )
{
    LONG        lcbRet;
        CHAR            achRoot[4];
        ULARGE_INTEGER  freeBytes;

        achRoot[0] = 'A'+nDrive-1;
        achRoot[1] = ':';
        achRoot[2] = '\\';
        achRoot[3] = 0;
        memset(&freeBytes, 0, sizeof(freeBytes));
        
        GetDiskFreeSpaceEx(achRoot, &freeBytes, 0, 0);
        lcbRet = freeBytes.LowPart;

    /* KLUDGE: Drives bigger than 2 GB can return zero total space!
    */
    if (lcbRet < 0L || lcbRet > (999999L * 1024L))
        {
        return (999999L * 1024L);
        }

    return (lcbRet);
}


/*
**  Purpose:
**      Creates and displays bootstrapper window.
**  Arguments:
**      hInstance: process instance handle
**  Returns:
**      Window handle to bootstrapper window, or
**      NULL if the window could not be created.
**************************************************************************/
HWND HwndInitBootWnd ( HANDLE hInstance )
{
    WNDCLASS    wc;
    HWND        hwnd;
    int         cx, cy;

    wc.style = 0;
    wc.lpfnWndProc = BootWndProc;
    wc.cbClsExtra = 0;
    wc.cbWndExtra = 0;
    wc.hInstance = hInstance;
    wc.hIcon = NULL;
    wc.hCursor = NULL;
    wc.hbrBackground = (HBRUSH)(COLOR_BTNFACE + 1);
    wc.lpszMenuName = NULL;
    wc.lpszClassName = szBootClass;
    if (!RegisterClass(&wc))
        return (NULL);

    cx = GetSystemMetrics(SM_CXSCREEN) / 2;
    cy = GetSystemMetrics(SM_CYSCREEN) / 3;

    hwnd = CreateWindow(szBootClass, rgchBootTitle,
            WS_DLGFRAME, cx / 2, cy, cx, cy, NULL, NULL, hInstance, NULL);

    if (hwnd == NULL)
        return (NULL);

    if (!fQuietMode)
        {
        ShowWindow(hwnd, SW_SHOWNORMAL);
        UpdateWindow(hwnd);
        }

    return (hwnd);
}

// ripped off from mvdm\wow32\wgtext.c
ULONG GetTextExtent(HDC hdc, LPSTR lpstr, int cbString)
{
    ULONG ul = 0;
    SIZE size4;

    if ((GetTextExtentPoint(
                    hdc,
                    lpstr,
                    cbString,
                    &size4
                   )))
    {
        // check if either cx or cy are bigger than SHRT_MAX == 7fff
        // but do it in ONE SINGLE check

        if ((size4.cx | size4.cy) & ~SHRT_MAX)
        {
            if (size4.cx > SHRT_MAX)
               ul = SHRT_MAX;
            else
               ul = (ULONG)size4.cx;

            if (size4.cy > SHRT_MAX)
               ul |= (SHRT_MAX << 16);
            else
               ul |= (ULONG)(size4.cy << 16);
        }
        else
        {
            ul = (ULONG)(size4.cx | (size4.cy << 16));
        }

    }
    return (ul);
}

/*
**  Purpose:
**      WndProc for bootstrapper window.
**  Arguments:
**      Standard Windows WndProc arguments.
**  Returns:
**      Result of call DefWindowProc, or zero if WM_PAINT message.
**************************************************************************/
LRESULT CALLBACK BootWndProc ( HWND hwnd, UINT wMsgID, WPARAM wParam,
                            LPARAM lParam )
{
    HDC         hdc;
    PAINTSTRUCT ps;
    RECT        rect;
    UINT        iMargin;

    switch (wMsgID)
        {
#ifdef DBCS     // [J3] Fixed KK raid #12.
        case WM_CREATE:
            {
            if (!fQuietMode)
                {
                int x, y, cx, cy;
                hdc = BeginPaint(hwnd, &ps);
                GetClientRect(hwnd, &rect);
                cx = (LOWORD(GetTextExtent(hdc, rgchBootMess, lstrlen(rgchBootMess))) + 13) / 14 * 16 + 2;
                if (cx > rect.right)
                    {
                    if (cx > GetSystemMetrics(SM_CXSCREEN))
                        cx = GetSystemMetrics(SM_CXSCREEN);
                    x = (GetSystemMetrics(SM_CXSCREEN) - cx) / 2;
                    y = cy = GetSystemMetrics(SM_CYSCREEN) / 3;
                    SetWindowPos(hwnd, NULL, x, y, cx, cy, SWP_NOZORDER);
                    }
                EndPaint(hwnd, &ps);
                }
            break;
            }
#endif
        case WM_PAINT:
            if (!fQuietMode)
                {
                hdc = BeginPaint(hwnd, &ps);
                GetClientRect(hwnd, &rect);
                iMargin = rect.right / 16;
                rect.top    = rect.bottom / 2 - GetSystemMetrics(SM_CYCAPTION);
                rect.left   = iMargin;
                rect.right -= iMargin;
                SetBkMode(hdc, TRANSPARENT);
                DrawText(hdc, rgchBootMess, -1, &rect,
                         DT_WORDBREAK | DT_CENTER | DT_NOPREFIX);
                EndPaint(hwnd, &ps);
                }
            break;
        default:
            return (DefWindowProc(hwnd, wMsgID, wParam, lParam));
        }

    return (0L);
}


/*
**  Purpose:
**      Get size of file.
**  Arguments:
**      szFile:  List file name (full path, ANSI).
**      pcbSize: Pointer to variable to receive file size.
**  Returns:
**      FALSE if file found and size >= 64K.
**      TRUE otherwise.
**************************************************************************/
BOOL FGetFileSize ( char * szFile, UINT * pcbSize )
{
    int     fh;
    int     fErr;
    LONG    lcb;

    *pcbSize = 0;
    if ((fh = _lopen(szFile, OF_READ)) == -1)
        {
        return (TRUE);
        }
    
    if ((lcb = _llseek(fh, 0L, 2)) > 65535)
        {
#pragma warning(disable:4127)   /* conditional expression is constant */
        Assert(FALSE);
#pragma warning(default:4127)
        _lclose(fh);
        return (FALSE);
        }
    *pcbSize = (UINT)lcb;
    fErr = _lclose(fh);
    Assert(!fErr);

    return (TRUE);
}


/*
**  Purpose:
**      Build file Src and Dst lists from LST file.
**  Arguments:
**      szFile: List file name (full path, ANSI).
**      cbFile: Size of list file
**  Note:
**      Sets globals: hSrcLst, hDstLst.
**  Returns:
**      One of the following Bootstrapper return codes:
**          brcMem    out of memory
**          brcLst    list file is corrupted
**          brcOkay   completed without error
**************************************************************************/
BRC BrcBuildFileLists ( char * szFile, UINT cbFile )
{
    char    rgchDst[cchLstLineMax];
    char *  szSrc;
    char *  szDst;
    char *  pchDstStart;
    int     cbSrc;
    UINT    i;

    /* Build Src List */

    if ((hSrcLst = LocalAlloc(LMEM_MOVEABLE, cbFile)) == NULL)
        return (brcMem);
    szSrc = (char *)LocalLock(hSrcLst);
    if(szSrc == (char *)NULL)
        return (brcMem);

    i = GetPrivateProfileString(szFilesSect, NULL, "", szSrc, cbFile, szFile);
    if (i <= 0)
        {
        LocalUnlock(hSrcLst);
        hSrcLst = LocalFree(hSrcLst);
        Assert(hSrcLst == NULL);
        return (brcLst);
        }
    Assert(i+1 < cbFile);
    szSrc[i++] = '\0';  /* force double zero at end */
    szSrc[i++] = '\0';
    LocalUnlock(hSrcLst);
    hSrcLst = LocalReAlloc(hSrcLst, i, LMEM_MOVEABLE);
    if(hSrcLst == NULL)
        return (brcMem);

    /* Build Dst List */
    if ((hDstLst = LocalAlloc(LMEM_MOVEABLE, cbFile)) == NULL)
        {
        hSrcLst = LocalFree(hSrcLst);
        Assert(hSrcLst == NULL);
        return (brcMem);
        }

    szSrc = (char *)LocalLock(hSrcLst);
    if (szSrc == (char *)NULL)
        return (brcMem);

    szDst = pchDstStart = (char *)LocalLock(hDstLst);
    if (szDst == (char *)NULL) {
        LocalUnlock (hDstLst);
        return (brcMem);
    }

    for (;
        (cbSrc = lstrlen(szSrc)) != 0;
        szSrc += cbSrc + 1, szDst += lstrlen(szDst) + 1)
        {
        if (GetPrivateProfileString(szFilesSect, szSrc, "", rgchDst,
                cchLstLineMax, szFile) <= 0)
            {
            LocalUnlock(hSrcLst);
            LocalUnlock(hDstLst);
            FreeFileLists();
            return (brcLst);
            }
        AnsiToOem(rgchDst, rgchDst);
        lstrcpy(szDst, rgchDst);
        }
    *szDst = '\0';  /* force double zero at end */
    LocalUnlock(hSrcLst);
    LocalUnlock(hDstLst);
    hDstLst = LocalReAlloc(hDstLst, (int)(szDst - pchDstStart) + 1,
            LMEM_MOVEABLE);
    if (hDstLst == NULL)
        return (brcMem);

    return (brcOkay);
}


/*
**  Purpose:
**      Frees file list buffers with non-NULL handles
**      and sets them to NULL.
**  Arguments:
**      none.
**  Returns:
**      none.
**************************************************************************/
VOID FreeFileLists ()
{
    if (hSrcLst != NULL)
        hSrcLst = LocalFree(hSrcLst);
    if (hDstLst != NULL)
        hDstLst = LocalFree(hDstLst);
    Assert(hSrcLst == NULL);
    Assert(hDstLst == NULL);
}



/*
**  Purpose:
**      Spawns off a process with WinExec and waits for it to complete.
**  Arguments:
**      szCmdLn: Line passed to WinExec (cannot have leading spaces).
**  Returns:
**      TRUE if successful, FALSE if not.
+++
**  Implementation:
**      GetModuleUsage will RIP under Win 3.0 Debug if module count is
**      zero (okay to ignore and continue), but the GetModuleHandle
**      check will catch all zero cases for single instances of the
**      driver, the usual case.  [Under Win 3.1 we will be able to
**      replace both checks with just an IsTask(hMod) check.]
**************************************************************************/
BOOL FExecAndWait ( char * szCmdLn, HWND hwndHide )
{
    UINT hMod;
    MSG msg;

    Assert(!isspace(*szCmdLn)); /* leading space kills WinExec */

    if ((hMod = WinExec(szCmdLn, SW_SHOWNORMAL)) > 32)
        {
        UINT i;
        UINT_PTR idTimer;
        
        /* KLUDGE: Give the app some time to create its main window.
        *
        *   On newer versions of NT, we were exiting the while loop
        *   (below) and cleaning up the temp dir before the app had
        *   even put up its window.
        *
        *   NOTE: In trials, we only had to retry once, so cRetryMax
        *   may be overkill, but it should be pretty rare that this
        *   would fail in shipping products anyway.
        */
        for (i = 0; i < cRetryMax; i++)
            {
            if(FindWindow(rgchDrvWinClass, NULL) != NULL)
                break;
            if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            }

        /* Set the timer to fire every 1/10 of a second. This is
            necessary because we might never return from GetMessage */
        idTimer = SetTimer(NULL, 0, 100, NULL);
        /*
        ** REVIEW - FindWindow() will wait until the LAST setup quits (not
        **  necessarily this setup.  If exec'ing a 16-bit app we could
        **  use the old code:
        **    while (GetModuleHandle(rgchDrvModName) && GetModuleUsage(hMod))
        **  but on NT this fails so for 32-bit apps we could attempt to
        **  remove one of the executable files (slow?).
        **
        ** REVIEW - This loop becomes a busy wait under NT, which is bad.
        **  However, it doesn't appear to affect ACME's performance
        **  noticeably.
        */
        while (FindWindow(rgchDrvWinClass, NULL) != NULL)
            {
            if (GetMessage(&msg, NULL, 0, 0))
                {
                TranslateMessage(&msg);
                DispatchMessage(&msg);
                }
            if (msg.message == WM_TIMER && hwndHide != (HWND)NULL)
                {
                ShowWindow(hwndHide, SW_HIDE);
                hwndHide = (HWND)NULL;
                }
            }
        if (idTimer != 0)
            KillTimer(0, idTimer);
        return (TRUE);
        }
#ifdef DEBUG
    wsprintf(szDebugBuf, "WinExec Error: %d", hMod);
    MessageBox(NULL, szDebugBuf, szDebugMsg, MB_OK | MB_ICONSTOP);
#endif  /* DEBUG */

    return (FALSE);
}


/*
**  Purpose: Processes messages that may be in the queue.
**  Arguments: none
**  Returns: none
**************************************************************************/
void PUBLIC FYield ( VOID )
{
    MSG  msg;

    while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
        {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
        }
}


/*
**************************************************************************/
BOOL FLstSectionExists ( char * szLstFileName, char * szSect )
{
    return (GetPrivateProfileString(szSect, "CmdLine", "", rgchCmdLine,
                cchLstLineMax, szLstFileName) > 0);
}



/*
**************************************************************************/
DWORD GetCpuArchitecture ()
{
    SYSTEM_INFO sysInfo;

    GetSystemInfo(&sysInfo);

    return sysInfo.wProcessorArchitecture;
}


static CSZC cszcBootstrapperKey = "MS Setup (ACME)\\Bootstrapper\\Exit Level";
static CSZC cszcEelRunning      = "Running";

/*
**  Purpose:
**      Lets Acme know the bootstrapper launched it.  So Acme will let
**      us know its exit error level.
**  Arguments:
**      none.
**  Returns:
**      fTrue if successful, fFalse otherwise.
**  Notes:
**      REVIEW: Probably should use DDE instead of the Registration
**      Database.
**************************************************************************/
BOOL FNotifyAcme ( VOID )
{
    if (!FCreateRegKey(cszcBootstrapperKey))
        {
        return (fFalse);
        }
    if (!FCreateRegKeyValue(cszcBootstrapperKey, cszcEelRunning))
        {
        return (fFalse);
        }
    if (!FFlushRegKey())
        {
        return (fFalse);
        }
    return (fTrue);
}


/*
**  Purpose:
**      Get the exit error level set by Acme and clean up the Registration
**      Database.
**  Arguments:
**      peel: Exit error level (to be set).
**  Returns:
**      fTrue if successful, fFalse otherwise.
**************************************************************************/
BOOL FGetAcmeErrorLevel ( EEL * peel )
{
    CHAR rgchValue[cchSzMax];

    if (FGetRegKeyValue(cszcBootstrapperKey, rgchValue, sizeof rgchValue))
        {
#ifdef DEBUG
        /*
         *  Assert(isdigit(rgchValue[0]));
         *  Assert(isdigit(rgchValue[1]) || rgchValue[1] == chEos);
         */
        UINT i;
        BOOL fValidValue = fFalse;

        /*  Assumes valid values are 1 or 2 digit numbers. */
        for (i = 0; rgchValue[i] != chEos; i++)
            {
            fValidValue = fTrue;
            if (!isdigit(rgchValue[i]) || i > 1)
                {
                fValidValue = fFalse;
                break;
                }
            }
        if (!fValidValue)
            {
            char szBuf[cchSzMax];
    
            wsprintf(szBuf, "RegKeyValue (%s)", rgchValue);
            MessageBox(NULL, szBuf, "Debug Assertion in FGetAcmeErrorLevel",
                        MB_OK | MB_ICONSTOP);
            }
#endif  /* DEBUG */

        *peel = atoi(rgchValue);
        DeleteRegKey(cszcBootstrapperKey);
        return (fTrue);
        }
    else
        {
        if (fWin31)
            {
            *peel = eelSuccess;
            return fTrue;
            }
        return (fFalse);
        }
}


/*
**  Purpose:
**      Creates a Registration Database key that is a subkey of
**      cszcBootstrapperKey.
****************************************************************************/
BOOL FCreateRegKey ( CSZC cszcKey )
{
    HKEY hkey;

    if (RegCreateKey(HKEY_CLASSES_ROOT, cszcKey, &hkey) != ERROR_SUCCESS)
        {
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        return (fFalse);
        }

    if (RegCloseKey(hkey) != ERROR_SUCCESS)
        {
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        return (fFalse);
        }

    return (fTrue);
}


/*
**  Purpose:
**      API to check for the existence of the specified key in
**      the Registration Database.
****************************************************************************/
BOOL FDoesRegKeyExist ( CSZC cszcKey )
{
    HKEY hkey;

    if (RegOpenKey(HKEY_CLASSES_ROOT, cszcKey, &hkey) != ERROR_SUCCESS)
        return (fFalse);

    RegCloseKey(hkey);

    return (fTrue);
}


/*
**  Purpose:
**      Creates a Registration Database key that is a subkey of
**      HKEY_CLASSES_ROOT and associates a value with the key.
****************************************************************************/
BOOL FCreateRegKeyValue ( CSZC cszcKey, CSZC cszcValue )
{
    if (RegSetValue(HKEY_CLASSES_ROOT, cszcKey, REG_SZ, cszcValue,
                    lstrlen(cszcKey)) != ERROR_SUCCESS)
        {
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        return (fFalse);
        }

    return (fTrue);
}


/*
**  Purpose:
**      Determines the value associated with the specified Registration
**      Database key.
****************************************************************************/
BOOL FGetRegKeyValue ( CSZC cszcKey, SZ szBuf, CB cbBufMax )
{
    LONG lcb = cbBufMax;

    if (szBuf != szNull && cbBufMax != 0)
        *szBuf = chEos;

    if (!FDoesRegKeyExist(cszcKey))
        return (fFalse);

    if (RegQueryValue(HKEY_CLASSES_ROOT, cszcKey, szBuf, &lcb)
            != ERROR_SUCCESS)
        {
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        return (fFalse);
        }

    Assert(lcb < cbMaxConst);

    return (fTrue);
}


/*
**  Purpose:
**      API to remove the specified Registration Database key,
**      its associated values, and subkeys.
****************************************************************************/
VOID DeleteRegKey ( CSZC cszcKey )
{
    char rgchKey[cchSzMax], rgchBuffer[cchSzMax];
    char *pch;
    HKEY hKeyT = NULL;
        
    lstrcpy(rgchKey, cszcKey);
    RegDeleteKey(HKEY_CLASSES_ROOT, rgchKey);
    pch = rgchKey + 1;

    while(pch > rgchKey)
        {
        pch = rgchKey + lstrlen(rgchKey);
        while (pch > rgchKey)
            {
            if (*pch == '\\')
                break;
            pch--;
            }
        if (*pch != '\\')
            break;
        *pch = '\0';
        if (RegOpenKey(HKEY_CLASSES_ROOT, rgchKey, &hKeyT) != ERROR_SUCCESS)
            break;
        if (RegEnumKey(hKeyT, 0, rgchBuffer, sizeof(rgchBuffer)) == ERROR_SUCCESS)
            {
            break;
            }

        RegCloseKey(hKeyT);
        hKeyT = NULL;
        RegDeleteKey(HKEY_CLASSES_ROOT, rgchKey);
        }

    if (hKeyT != NULL)
        RegCloseKey(hKeyT);
    
}


/*
**  Purpose:
**      API to flush the specified Registration Database key.
****************************************************************************/
BOOL FFlushRegKey ( VOID )
{
    /* REVIEW: Does 16 bit code need to flush the RegDb?  RegFlushKey is
        32 bit.
    if (RegFlushKey(HKEY_CLASSES_ROOT)) != ERROR_SUCCESS)
        {
        DispErrBrc(brcRegDb, TRUE, MB_OK | MB_ICONSTOP, NULL, NULL, NULL);
        return (fFalse);
        }
    */

    return (fTrue);
}


/*
**  Purpose:
**      Write temporary files to restart ini file.  So that if Acme reboots,
**      the files in the temporary directory will be removed.  Win95 only.
**  Arguments:
**      szTmpDir: Full path to destination directory (OEM chars).
**  Returns:
**      fTrue if successful, fFalse otherwise.
**
**  REVIEW: The files are removed, but not the temp directories.
**      There may be a way to do that via the wininit.ini file.
**      This should be looked into.
**************************************************************************/
BOOL FWriteToRestartFile ( SZ szTmpDir )
{
    char   rgchIniFile[_MAX_PATH];
    CB     cbFrom;
    CB     cbTo;
    HLOCAL hlocalFrom = (HLOCAL)NULL;
    HLOCAL hlocalTo   = (HLOCAL)NULL;
    BOOL   fRet = fFalse;

    SZ szSection = "rename";
    SZ szKey     = "NUL";

    /* This code is not used under NT. */
    if (1)
        {
        return (fTrue);
        }

    if (!FCreateIniFileName(rgchIniFile, sizeof rgchIniFile))
        {
        goto LCleanupAndReturn;
        }
    if (!FGetFileSize(rgchIniFile, &cbFrom))
        {
        goto LCleanupAndReturn;
        }
    if (!FReadIniFile(rgchIniFile, &hlocalFrom, &cbFrom))
        {
        goto LCleanupAndReturn;
        }
    if (!FAllocNewBuf(cbFrom, szTmpDir, szSection, szKey, &hlocalTo, &cbTo))
        {
        goto LCleanupAndReturn;
        }
    if (!FProcessFile(hlocalFrom, hlocalTo, cbTo, szTmpDir, szSection, szKey))
        {
        goto LCleanupAndReturn;
        }
    if (!FWriteIniFile(rgchIniFile, hlocalTo))
        {
        goto LCleanupAndReturn;
        }
    fRet = fTrue;

LCleanupAndReturn:
    if (hlocalFrom != (HLOCAL)NULL)
        {
        hlocalFrom = LocalFree(hlocalFrom);
        Assert(hlocalFrom == (HLOCAL)NULL);
        }
    if (hlocalTo != (HLOCAL)NULL)
        {
        hlocalTo = LocalFree(hlocalTo);
        Assert(hlocalTo == (HLOCAL)NULL);
        }

    return (fRet);
}


/*
**  Purpose:
**      Create the restart file name.
**  Arguments:
**      szIniFile: Buffer to hold file name.
**      cbBufMax:  Size of buffer.
**  Returns:
**      fTrue if successful, fFalse otherwise.
**************************************************************************/
BOOL FCreateIniFileName ( SZ szIniFile, CB cbBufMax )
{
    CB cbWinDir;

    cbWinDir = GetWindowsDirectory((LPSTR)szIniFile, cbBufMax);
    if (cbWinDir == 0)
        {
#pragma warning(disable:4127)   /* conditional expression is constant */
        Assert(fFalse); /*  Unusual if this happens. */
#pragma warning(default:4127)
        return (fFalse);
        }
    Assert(isalpha(*szIniFile));
    Assert(*(szIniFile + 1) == ':');

    if (*(AnsiPrev((LPSTR)szIniFile, (LPSTR)&szIniFile[cbWinDir])) != '\\')
        lstrcat((LPSTR)szIniFile, "\\");
    lstrcat((LPSTR)szIniFile, "wininit.ini");
    Assert((CB)lstrlen(szIniFile) < cbBufMax);

    return (fTrue);
}


/*
**  Purpose:
**      Read the data from the ini file
**  Arguments:
**      szIniFile: Ini file name
**      phlocal:   Pointer to memory handle.
**      pcbBuf:    Pointer to the number of bytes in the buffer.
**  Returns:
**      fTrue if successful, fFalse otherwise.
**************************************************************************/
BOOL FReadIniFile ( SZ szIniFile, HLOCAL * phlocal, PCB pcbBuf )
{
    UINT   fModeSav;
    HLOCAL hlocal;
    SZ     szBuf;
    CB     cbBuf;
    BOOL   fRet = fFalse;

    Assert(szIniFile != szNull);
    Assert(phlocal != (HLOCAL *)NULL);
    Assert(pcbBuf  != pcbNull);

    fModeSav = SetErrorMode(fNoErrMes);
    hlocal = *phlocal;
    cbBuf  = *pcbBuf;

    Assert(hlocal == (HLOCAL)NULL);

    if (cbBuf == 0) /* Ini file does not exist or is empty. */
        {
        /*  Alloc room for CR, LF, EOS. */
        hlocal = LocalAlloc(LMEM_MOVEABLE, 3);
        if (hlocal == NULL)
            {
#ifdef DEBUG
            MessageBox(NULL, "Out of memory in FReadIniFile.", szDebugMsg,
                MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
            }
        else
            {
            szBuf = (SZ)LocalLock(hlocal);
            
            if(szBuf == szNull)
                return fFalse;

            *szBuf++ = chCR;
            *szBuf++ = chEol;
            *szBuf   = chEos;
            *pcbBuf = 2;
            fRet = fTrue;
            }
        }
    else
        {
        HFILE    hfile;
        OFSTRUCT ofs;
        CB       cbRead;

        /* Flush cache before calling OpenFile() */
        WritePrivateProfileString(szNull, szNull, szNull, szIniFile);
        hfile = OpenFile(szIniFile, &ofs, OF_READWRITE | OF_SHARE_EXCLUSIVE);
        if (hfile == HFILE_ERROR)
            {
#ifdef DEBUG
            wsprintf(szDebugBuf, "Can't open file: %s.", szIniFile);
            MessageBox(NULL, szDebugBuf, szDebugMsg,
                            MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
            goto LCleanupAndReturn;
            }
        hlocal = LocalAlloc(LMEM_MOVEABLE, cbBuf + 1);
        if (hlocal == NULL)
            {
#ifdef DEBUG
        MessageBox(NULL, "Out of memory in FReadIniFile.", szDebugMsg,
            MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
            }
        else
            {
            szBuf = (SZ)LocalLock(hlocal);
            if(szBuf == szNull)
                return fFalse;

            cbRead = (CB)_lread(hfile, szBuf, cbBuf + 1);
            if (cbRead == HFILE_ERROR)
                {
#ifdef DEBUG
                wsprintf(szDebugBuf, "Can't read file: %s.", szIniFile);
                MessageBox(NULL, szDebugBuf, szDebugMsg,
                                MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
                }
            else
                {
                Assert(cbRead == cbBuf);
                *(szBuf + cbBuf) = chEos;
                fRet = fTrue;
                }
            }
        hfile = _lclose(hfile);
        Assert(hfile != HFILE_ERROR);
        }

LCleanupAndReturn:
    if (hlocal != NULL)
        {
        LocalUnlock(hlocal);
        }
    *phlocal = hlocal;
    SetErrorMode(fModeSav);

    return (fRet);
}


/*
**  Purpose:
**      Allocate buffer for new file.
**  Arguments:
**      cbOld:     Size of existing file
**      szTmpDir:  Full path to destination directory (OEM chars).
**      szSection: Ini section name
**      szKey:     Ini key name
**      phlocal:   Pointer to memory handle.
**      pcbToBuf:  Pointer to total size of new buffer.
**  Returns:
**      fTrue if successful, fFalse if LocalAlloc failed.
**************************************************************************/
BOOL FAllocNewBuf ( CB cbOld, SZ szTmpDir, SZ szSection, SZ szKey,
                    HLOCAL * phlocal, PCB pcbToBuf )
{
    UINT fModeSav;
    SZ   szDst;
    CB   cbDst;
    CB   cbOverhead;
    CB   cbNew;
    BOOL fRet = fFalse;

    fModeSav = SetErrorMode(fNoErrMes);
    szDst = (SZ)LocalLock(hDstLst);
    if(szDst == szNull)
        return fFalse;
    /*
     *  Added to the old file will be one line per temporary file
     *  and (possibly) a section line.  cbNew is initialized with
     *  the size of the section line, plus enough for the file
     *  (_MSSETUP._Q_) which is not in the DstLst.
     *
     *  Each line will look like:
     *      <szKey>=<szTmpDir>\<szFile><CR><LF>
     */
    cbOverhead = lstrlen(szKey) + 1 + lstrlen(szTmpDir) + 1 + 2;
    cbNew = lstrlen(szSection) + 5 + _MAX_PATH;
    for (; (cbDst = lstrlen(szDst)) != 0; szDst += cbDst + 1)
        {
        cbNew += cbOverhead + cbDst;
        }

    LocalUnlock(hDstLst);

    *pcbToBuf = cbOld + cbNew;
    *phlocal = LocalAlloc(LMEM_MOVEABLE, *pcbToBuf);
    if (*phlocal == NULL)
        {
#ifdef DEBUG
        MessageBox(NULL, "Out of memory in FAllocNewBuf.", szDebugMsg,
            MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
        }
    else
        fRet = fTrue;
    SetErrorMode(fModeSav);

    return (fRet);
}


/*
**  Purpose:
**      Add the new lines to the ini file.
**  Arguments:
**      hlocalFrom: Handle to Src memory.
**      hlocalTo:   Handle to Dst memory.
**      cbToBuf:    Total size of Dst memory.
**      szTmpDir:   Full path to destination directory (OEM chars).
**      szSection:  Ini section name
**      szKey:      Ini key name
**  Returns:
**      fTrue if successful, fFalse otherwise.
**
**  REVIEW: DBCS writes out different order.  See DBCS J6 code and
**          comments in sysinicm.c.
**************************************************************************/
BOOL FProcessFile ( HLOCAL hlocalFrom, HLOCAL hlocalTo, CB cbToBuf,
                        SZ szTmpDir, SZ szSection, SZ szKey )
{
    UINT fModeSav;
    SZ   szFromBuf;
    SZ   szToBuf;
    SZ   szToStart;
    SZ   szCur;
    SZ   szDst;
    CB   cbSect;
    CB   cbDst;

    Unused(cbToBuf);    /* Used in debug only */

    fModeSav = SetErrorMode(fNoErrMes);

    szFromBuf = (SZ)LocalLock(hlocalFrom);
    if(szFromBuf == szNull)
        return fFalse;

    szToBuf = (SZ)LocalLock(hlocalTo);
    if(szToBuf != szNull) {
        LocalUnlock (hlocalFrom);
        return fFalse;
    }

    szToStart = szToBuf;

    cbSect = lstrlen(szSection);
    for (szCur = szFromBuf; *szCur != chEos; szCur = AnsiNext(szCur))
        {
        if (*szCur == '[' && *((szCur + cbSect + 1)) == ']'
                && _memicmp(szSection, AnsiNext(szCur), cbSect) == 0)
            {
            /*  Found section.  Copy up to section line. */
            CB cbCopy = (CB)(szCur - szFromBuf);

            memcpy(szToBuf, szFromBuf, cbCopy);
            szToBuf += cbCopy;
            break;
            }
        }

    /*  Copy section line. */
    *szToBuf++ = '[';
    memcpy(szToBuf, szSection, cbSect);
    szToBuf += cbSect;
    *szToBuf++ = ']';
    *szToBuf++ = chCR;
    *szToBuf++ = chEol;

    /*  Copy new lines. */
    szDst = (SZ)LocalLock(hDstLst);
    if (szDst == szNull) {

        LocalUnlock(hlocalFrom);
        LocalUnlock(hlocalTo);
        return fFalse;
    }

    for (; (cbDst = lstrlen(szDst)) != 0; szDst += cbDst + 1)
        {
        CopyIniLine(szKey, szTmpDir, szDst, &szToBuf);
        }
    LocalUnlock(hDstLst);
    CopyIniLine(szKey, szTmpDir, "_MSSETUP._Q_", &szToBuf);

    /*  Copy rest of file. */
    if (*szCur == '[')
        {
        /*
         *  Skip section line in From buffer.  Allow room for '[', section,
         *  ']', CR, LF.
         */
        szCur += cbSect + 4;
        }
    else
        {
        szCur = szFromBuf;
        }
    szToBuf = _memccpy(szToBuf, szCur, chEos, UINT_MAX);
    Assert(szToBuf != szNull);
    Assert((CB)lstrlen(szToStart) < cbToBuf);

    LocalUnlock(hlocalFrom);
    LocalUnlock(hlocalTo);
    SetErrorMode(fModeSav);

    return (fTrue);
}


/*
**  Purpose:
**      Constructs and copies an ini line to a buffer.
**  Arguments:
**      szKey:    Ini key name
**      szTmpDir: Full path to destination directory (OEM chars).
**      szFile:   Name of file in temporary directory.
**      pszToBuf: Pointer to new buffer.
**  Returns:
**      none
**************************************************************************/
VOID CopyIniLine ( SZ szKey, SZ szTmpDir, SZ szFile, PSZ pszToBuf )
{
    char rgchSysIniLine[256];
    CB   cbCopy;

    lstrcpy(rgchSysIniLine, szKey);
    lstrcat(rgchSysIniLine, "=");
    lstrcat(rgchSysIniLine, szTmpDir);
    lstrcat(rgchSysIniLine, "\\");
    lstrcat(rgchSysIniLine, szFile);
    Assert(lstrlen(rgchSysIniLine) < sizeof rgchSysIniLine);
    cbCopy = lstrlen(rgchSysIniLine);
    memcpy(*pszToBuf, rgchSysIniLine, cbCopy);
    (*pszToBuf) += cbCopy;
    *(*pszToBuf)++ = chCR;
    *(*pszToBuf)++ = chEol;
}


/*
**  Purpose:
**      Writes out the new ini file.
**  Arguments:
**      szIniFile: Buffer to hold file name.
**      hlocalTo:   Handle to Src memory.
**  Returns:
**      fTrue if successful, fFalse otherwise.
**************************************************************************/
BOOL FWriteIniFile ( SZ szIniFile, HLOCAL hlocalTo )
{
    UINT     fModeSav;
    SZ       szToBuf;
    HFILE    hfile;
    OFSTRUCT ofs;
    CB       cbWrite;
    BOOL     fRet = fFalse;

    fModeSav = SetErrorMode(fNoErrMes);
    szToBuf = (SZ)LocalLock(hlocalTo);
    if(szToBuf == szNull)
        return fFalse;

    hfile = OpenFile(szIniFile, &ofs, OF_CREATE | OF_WRITE);
    if (hfile == HFILE_ERROR)
        {
#ifdef DEBUG
        wsprintf(szDebugBuf, "Can't open file: %s.", szIniFile);
        MessageBox(NULL, szDebugBuf, szDebugMsg,
                        MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
        goto LUnlockAndReturn;
        }

    cbWrite = _lwrite(hfile, szToBuf, lstrlen(szToBuf));
    if (cbWrite == HFILE_ERROR)
        {
#ifdef DEBUG
        wsprintf(szDebugBuf, "Can't write to file: %s.", szIniFile);
        MessageBox(NULL, szDebugBuf, szDebugMsg,
                        MB_OK | MB_ICONEXCLAMATION);
#endif /* DEBUG */
        }
    else
        {
        fRet = fTrue;
        }

    hfile = _lclose(hfile);
    Assert(hfile != HFILE_ERROR);

LUnlockAndReturn:
    LocalUnlock(hlocalTo);
    SetErrorMode(fModeSav);

    return (fRet);
}

CHAR szcStfSrcDir[] = "Source Directory\t";
#define cchStfSrcDir (sizeof(szcStfSrcDir)-1)

/* Finds the source directory for the installation, asks the user
   to insert the disk. And returns */

BRC BrcInsertDisk(CHAR *pchStf, CHAR *pchSrcDrive)
{
    CHAR  rgbBuf[_MAX_PATH];
    BYTE  rgbFileBuf[32];
    UINT  iFileBuf = sizeof(rgbFileBuf), cFileBuf = sizeof(rgbFileBuf);
    CHAR *pchBuf = rgbBuf;
    CHAR *pchMsg;
    int iStf = 0;
    HFILE hFile;
    BRC brc = brcLst;
    char chDrv;
    BOOL fQuote = FALSE;
    int drvType;
    BOOL fFirst = TRUE;
    BOOL fOpen = FALSE;
    HFILE hFileT;
    BOOL fRenameStf = fFalse;

    if ((hFile = _lopen(pchStf, OF_READ)) == HFILE_ERROR)
        return brcNoStf;

    /* Find the path to the original setup. This is stored in the .stf file on the
       Source Directory line */
    while (pchBuf < rgbBuf + sizeof(rgbBuf))
        {
        BYTE ch;

        if (iFileBuf == cFileBuf)
            {
            if ((cFileBuf = _lread(hFile, rgbFileBuf, sizeof(rgbFileBuf))) == 0)
                goto LDone;
            iFileBuf = 0;
            }
        ch = rgbFileBuf[iFileBuf++];
        if (iStf < cchStfSrcDir)
            {
            if (ch == szcStfSrcDir[iStf])
                iStf++;
            else
                iStf = 0;
            continue;
            }
        if(fQuote)
            fQuote = FALSE;
        else if (ch == '"')
            {
            fQuote = TRUE;
            continue;
            }
        else if (ch == '\x0d' || ch == '\t')
            break;
        *pchBuf++ = (CHAR)ch;
        /* Case of having the last character be a DBCS character */
        if (IsDBCSLeadByte(ch))
            {
            if (iFileBuf == cFileBuf)
                {
                _lread(hFile, &ch, 1);
                *pchBuf++ = (CHAR) ch;
                }
            else
                *pchBuf++ = rgbFileBuf[iFileBuf++];
            }
        }

LDone:
    *pchBuf = 0;
    if (rgbBuf[0] == 0)
        {
        fRenameStf = fTrue;
        goto LClose;
        }
    chDrv = (char)toupper(rgbBuf[0]);
    if (rgbBuf[1] != ':' || chDrv < 'A' || chDrv > 'Z')
        {
        /* We know this is a network drive - UNC Name */
        drvType = EX_DRIVE_REMOTE;
        Assert(rgbBuf[0] == '\\' && rgbBuf[1] == '\\');
        }
    else
        {
        drvType = GetDriveTypeEx(chDrv - 'A');
        }

    lstrcpy(pchSrcDrive, rgbBuf);
    if (*AnsiPrev(rgbBuf, pchBuf) != '\\')
        {
        *pchBuf++ = '\\';
        *pchBuf = 0;
        }

    lstrcat(rgbBuf, "Setup.ini");

    while (!fOpen)
        {
        switch (drvType)
            {
        case EX_DRIVE_FIXED:
        case EX_DRIVE_REMOTE:
        case EX_DRIVE_RAMDISK:
        case EX_DRIVE_INVALID:
        default:
            if (!fFirst)
                {
                /* We've been here before */
                DispErrBrc(brcConnectToSource, TRUE, MB_OK | MB_ICONSTOP, pchSrcDrive, NULL, NULL);
                brc = brcMax;
                goto LClose;
                }
            /* The setup stuff should be available, change directories and go for it */
            break;
        case EX_DRIVE_FLOPPY:
        case EX_DRIVE_REMOVABLE:
            /* Ask to insert disk */
            pchMsg = rgchInsertDiskMsg;
            goto LAskUser;
            break;
        case EX_DRIVE_CDROM:
            /* Ask to insert their CD */
            pchMsg = rgchInsertCDMsg;
LAskUser:
            if (fFirst)
                {
                if (DispErrBrc(brcString, FALSE, MB_ICONEXCLAMATION|MB_OKCANCEL,
                        pchMsg, NULL, NULL) != IDOK)
                    {
                    brc = brcUserQuit;
                    goto LClose;
                    }
                }
            else
                {
                if (DispErrBrc(brcInsCDRom2, FALSE, MB_ICONEXCLAMATION|MB_OKCANCEL,
                        rgbBuf, pchMsg, NULL) != IDOK)
                    {
                    brc = brcUserQuit;
                    goto LClose;
                    }
                }
            break;
            }

        if ((hFileT = _lopen(rgbBuf, OF_READ)) != HFILE_ERROR)
            {
            _lclose(hFileT);
            fOpen = fTrue;
            }

        fFirst = FALSE;

        }

    brc = brcOkay;

LClose:
    _lclose(hFile);

    /* If we can't find the source path in the maintenance mode .STF,
    *   assume it's corrupted and rename it, so when the user runs again
    *   from the source image, we will just run in 'floppy' mode,
    *   avoiding the bad .STF file.
    *   (NOTE: Assumes /W is only used in maint mode!!)
    */
    if (fRenameStf)
        {
        FRenameBadMaintStf(pchStf);
        brc = brcNoStf;
        }

    return brc;

}


/*
****************************************************************************/
BOOL FRenameBadMaintStf ( SZ szStf )
{
    CHAR rgch[_MAX_FNAME];

    _splitpath(szStf, szNull, szNull, rgch, szNull);
    if (*rgch == '\0')
        lstrcpy(rgch, "stf");

    Assert(lstrlen(rgch) + 4 < sizeof rgch);
    lstrcat(rgch, ".000");

    rename(szStf, rgch);

    /* Remove the original .STF in case the rename failed
    *   (probably due to a previously renamed .STF file).
    */
    remove(szStf);

    return (fTrue);     /* Always returns true */
}