/*
 *  Microsoft Confidential
 *  Copyright (C) Microsoft Corporation 1991
 *  All Rights Reserved.
 *
 *
 *  PIFMGR.C
 *  Main module for PIFMGR.DLL
 *
 *  History:
 *  Created 31-Jul-1992 3:30pm by Jeff Parsons
 *
 *  Exported Program Information File (PIF) Manager services:
 *
 *      PifMgr_OpenProperties()
 *          Give it the name of an DOS application (com, exe, or bat),
 *          and it will open the PIF associated with that application
 *          and return a "handle" to the app's "properties".  Use this
 *          handle when calling any of the other "properties" services (ie,
 *          Get, Set, and Close).
 *
 *          If no PIF exists, it will still allocate a PIF data block
 *          in memory and initialize it, either with data from _DEFAULT.PIF
 *          or its internal defaults.  It will also construct the PIF name
 *          it was looking for but couldn')t find and save that in its internal
 *          PIF data structure, so that if PifMgr_SetProperties is ever called, the
 *          data can be saved to disk.
 *
 *      PifMgr_GetProperties()
 *          Returns the specified block of data from the associated PIF.
 *          If it is a "named" block, it must be the name of a linked
 *          extension inside the PIF, which can be any predefined name
 *          (eg, "WINDOWS 386 3.0") or the name of your own block.  You can
 *          create your own named data blocks using the PifMgr_SetProperties()
 *          service.  "Named" data can also be thought of as "raw" data,
 *          because it is returned to the caller as-is -- without translation.
 *
 *          The size of a named block can be determined by calling
 *          PifMgr_GetProperties with a size of zero; no data is copied, but the size
 *          of the requested block is returned (0 if not found).
 *
 *          All named blocks can be enumerated by passing NULL for the name,
 *          a pointer to a 16-byte buffer for the requested block name, and a
 *          0-based block index in the size parameter.  The size returned
 *          is the size of the block (0 if none).
 *
 *          If an unnamed property block is requested (ie, the selector of
 *          the name parameter is NULL, and the offset is a property group
 *          ordinal), then the associated structure is returned.  For example,
 *          PifMgr_GetProperties(GROUP_TSK) returns a predefined structure (see
 *          PROPTSK in PIF.H) containing all the tasking-related information,
 *          in a format that is PIF-independent.  This is a valuable service,
 *          because it relieves callers from having to cope with PIFs
 *          containing a wide variety of sections (known as PIF extensions),
 *          only one of which is required.  Think of this as "cooked" data.
 *
 *          A third variation is raw read/write of the entire PIF data block,
 *          if lpszGroup is NULL.  This must be used with extreme caution, and
 *          will only be allowed if the properties were opened with the
 *          OPENPROPS_RAWIO flag specified.
 *
 *      PifMgr_SetProperties()
 *          This is pretty much the opposite of PifMgr_GetProperties, except that it
 *          also takes a flags parameter that can specify that the changes
 *          be made immediately, or deferred to PifMgr_CloseProperties.
 *
 *      PifMgr_CloseProperties()
 *          Flushes any dirty PIF data in memory, and frees the local heap
 *          storage.
 *
 */

#include "shellprv.h"
#pragma hdrstop

/* Global R/W DLL data
 */

PPROPLINK g_pplHead;              // pointer to first prop entry
HANDLE    g_offHighestPropLink;     // highest offset of a prop thus far recorded

TCHAR g_szNone[16];                // initialized by LibMainP,
TCHAR g_szAuto[16];                // and 16 chars to allow for localization

char g_szMSDOSSTSFile[] = "C:\\MSDOSSYS.STS";

TCHAR g_szConfigFile[] = TEXT("C:") CONFIGFILE;
TCHAR g_szAutoexecFile[] = TEXT("C:") AUTOEXECFILE;

TCHAR g_szMConfigFile[] = TEXT("C:") MCONFIGFILE;
TCHAR g_szMAutoexecFile[] = TEXT("C:") MAUTOEXECFILE;

TCHAR g_szWConfigFile[] = TEXT("C:") WCONFIGFILE;
TCHAR g_szWAutoexecFile[] = TEXT("C:") WAUTOEXECFILE;

#ifdef DBCS
char ImeBatchFile[] = "DOSIME\0";
#endif

#define NT_CONFIG_FILE "%SystemRoot%\\SYSTEM32\\CONFIG.NT"
#define NT_AUTOEXEC_FILE "%SystemRoot%\\SYSTEM32\\AUTOEXEC.NT"

#define LPPH_OFF(off) ((LPBYTE)lpph + off)
#define LPPIF_FIELDOFF(off) ((LPBYTE)ppl->lpPIFData + FIELD_OFFSET(PIFDATA,off))
#define LPPIF_OFF(off) ((LPBYTE)ppl->lpPIFData + off)

//
//  g_szDefaultPIF can be in one of three states:
//
//  1.  "_DEFAULT.PIF", which means that we have never needed to search
//          for a _default.pif yet.  The next time we need to locate
//          _default.pif, we must perform a full search.  On success,
//          move to state 2.  On failure, move to state 3.
//
//  2.  A fully-qualified path to _default.pif, which means that we have
//          searched for a _default.pif and found it in the specified
//          location.  The next time we need to locate _default.pif, we
//          will look here.  If found, remain in state 2, else move to
//          state 3.
//
//  3.  The null string, which means that we searched for a _default.pif
//          and didn't find one.  The next time we need to locate
//          _default.pif, we just fail without even looking on the disk.
//          (This is the common case for a clean install.)
//
//      Note that all the cases are "sticky"; once you reach a state, you
//      can never move back to a previous state.  This sacrifices flexibility
//      for performance.
//
//      The macro fTryDefaultPif() returns nonzero if we are in cases
//      1 or 2.
//
//      The macro fDefaultPifFound() returns nonzero if we are in case 2.
//
//  WARNING!  WARNING!  WARNING!  WARNING!
//
//      Evil hack relies on the fact that the three states can be
//      distinguished by the first character of g_szDefaultPIF, which
//      in turn relies on the fact that `_' cannot be the first character
//      of a fully-qualified path.  (It is not a valid drive letter,
//      and it cannot start a UNC.)
//
//

#define INIT_INIDATA                0x01
#define INIT_PIFDIR                 0x02

CHAR     fbInit = 0;                    // see INIT_* flags
INT      iPIFName = (12*sizeof(TCHAR)); // strlen(g_szPIFDir)
INT      iWinName = (12*sizeof(TCHAR)); // strlen(g_szPIFDir)
TCHAR    g_szPIFDir[MAXPATHNAME]     = TEXT("\\WINDOWS\\PIF");
TCHAR    g_szDefaultPIF[MAXPATHNAME] = TEXT("_DEFAULT.PIF");

#define fTryDefaultPif()            g_szDefaultPIF[0]
#define fDefaultPifFound()          (g_szDefaultPIF[0] != TEXT('_'))

//
// szComspec is the name of the COMSPEC program, usually "COMMAND.COM"
// or "CMD.EXE".
//
TCHAR   szComspec[8+1+3+1];

/* Global R/O DLL data
 */

extern const TCHAR c_szNULL[];              // A string so nice...

const TCHAR szZero[]            = TEXT("0");

const int acbData[] = {
                sizeof(PROPPRG),
                sizeof(PROPTSK),
                sizeof(PROPVID),
                sizeof(PROPMEM),
                sizeof(PROPKBD),
                sizeof(PROPMSE),
                sizeof(PROPSND),
                sizeof(PROPFNT),
                sizeof(PROPWIN),
                sizeof(PROPENV),
                sizeof(PROPNT31),
                sizeof(PROPNT40),
};

/*
 * The casts are used because we intentionally mis-prototyped the GetXxxData
 * and SetXxxData functions to receive their third argument as a LPXXX instead
 * of a LPVOID.
 */

const DATAGETFN afnGetData[] = {
                (DATAGETFN)GetPrgData,
                (DATAGETFN)GetTskData,
                (DATAGETFN)GetVidData,
                (DATAGETFN)GetMemData,
                (DATAGETFN)GetKbdData,
                (DATAGETFN)GetMseData,
                (DATAGETFN)GetSndData,
                (DATAGETFN)GetFntData,
                (DATAGETFN)GetWinData,
                (DATAGETFN)GetEnvData,
                (DATAGETFN)GetNt31Data,
                (DATAGETFN)GetNt40Data,
};

const DATASETFN afnSetData[] = {
                (DATASETFN)SetPrgData,
                (DATASETFN)SetTskData,
                (DATASETFN)SetVidData,
                (DATASETFN)SetMemData,
                (DATASETFN)SetKbdData,
                (DATASETFN)SetMseData,
                (DATASETFN)SetSndData,
                (DATASETFN)SetFntData,
                (DATASETFN)SetWinData,
                (DATASETFN)SetEnvData,
                (DATASETFN)SetNt31Data,
                (DATASETFN)SetNt40Data,
};


// WIN.INI things of interest
// Note: some of these NEED to be ANSI strings, and other TCHAR
// strings.  Please do not arbitrarily change the type casts of
// these strings!!!! (RickTu)


const TCHAR szMemory[]              = TEXT("MEMORY");
const TCHAR szComp[]                = TEXT("COMPATIBLE");

CHAR szSingle[]                     = "DOS=SINGLE\r\n";
CHAR szCRLF[]                       = "\r\n";
CHAR szEcho[]                       = "ECHO ";
CHAR szPause[]                      = "\r\nPAUSE\r\n";
CHAR szCall[]                       = "CALL ";
CHAR szCD[]                         = "CD ";
CHAR szWin[]                        = "WIN";

// SYSTEM.INI things of interest

const TCHAR szSystemINI[]           = TEXT("SYSTEM.INI");
const TCHAR sz386EnhSection[]       = TEXT("386Enh");
const TCHAR szWOAFontKey[]          = TEXT("WOAFont");
const TCHAR szWOADBCSFontKey[]      = TEXT("WOADBCSFont");
const TCHAR szNonWinSection[]       = TEXT("NonWindowsApp");
const TCHAR szTTInitialSizes[]      = TEXT("TTInitialSizes");
#ifdef  CUSTOMIZABLE_HEURISTICS
const TCHAR szTTHeuristics[]        = TEXT("TTHeuristics");
const TCHAR szTTNonAspectMin[]      = TEXT("TTNonAspectMin");
#endif
TCHAR szTTCacheSection[2][32] = {TEXT("TTFontDimenCache"), TEXT("TTFontDimenCacheDBCS")};

//
// These are because they are accessed only when we need to create
// a new PIF file or convert a 3.1 PIF file into a 4.0 PIF file.
//
const TCHAR szDOSAPPINI[]           = TEXT("DOSAPP.INI");
const TCHAR szDOSAPPSection[]       = TEXT("DOS Applications");
const TCHAR szDOSAPPDefault[]       = TEXT("Default");

const TCHAR szDisplay[]             = TEXT("DISPLAY");
const TCHAR szDefIconFile[]         = ICONFILE_DEFAULT;

const TCHAR szDotExe[]              = TEXT(".EXE");
const TCHAR szDotCom[]              = TEXT(".COM");
const TCHAR szDotBat[]              = TEXT(".BAT");
const TCHAR szDotPif[]              = TEXT(".PIF");
const TCHAR szDotCmd[]              = TEXT(".CMD");
const TCHAR * apszAppType[] =  {
    szDotExe, szDotCom, szDotBat, szDotCmd, szDotPif
};

CHAR szSTDHDRSIG[]                  = STDHDRSIG;
CHAR szW286HDRSIG30[]               = W286HDRSIG30;
CHAR szW386HDRSIG30[]               = W386HDRSIG30;
CHAR szWENHHDRSIG40[]               = WENHHDRSIG40;
CHAR szWNTHDRSIG31[]                = WNTHDRSIG31;
CHAR szWNTHDRSIG40[]                = WNTHDRSIG40;

CHAR szCONFIGHDRSIG40[]             = CONFIGHDRSIG40;
CHAR szAUTOEXECHDRSIG40[]           = AUTOEXECHDRSIG40;
const TCHAR szRunOnceKey[]          = REGSTR_PATH_RUNONCE;

const TCHAR szPIFConvert[]          = TEXT("PIFConvert");
const TCHAR szPIFConvertExe[]       = TEXT("RUNDLL.EXE PIFMGR.DLL,ProcessStartupProperties");
const TCHAR szPIFConvertKey[]       = REGSTR_PATH_PIFCONVERT;
const TCHAR szMSDOSMode[]           = REGSTR_VAL_MSDOSMODE;
const TCHAR szMSDOSModeDiscard[]    = REGSTR_VAL_MSDOSMODEDISCARD;


// wsprintf formatting strings
const TCHAR szDotPercent03d[]       = TEXT(".%03d");

// miscellaneous hack-o-ramas
const TCHAR szPP4[]                 = TEXT("PP4");      // MS Powerpoint 4.0

PROPTSK tskDefault          ={TSK_DEFAULT,
                              TSKINIT_DEFAULT,
                              TSKFGNDBOOST_DEFAULT,
                              TSKBGNDBOOST_DEFAULT,
                              0,
                              0,
                              TSKIDLESENS_DEFAULT,
};

PROPVID vidDefault          ={VID_DEFAULT,
                              VIDINIT_DEFAULT,
                              0,
                              0,
                              0,
};

PROPMEM memDefault          ={MEM_DEFAULT,
                              MEMINIT_DEFAULT,
                              MEMLOW_DEFAULT,   // ignore stdpifdata.minmem?
                              MEMLOW_MAX,       // ignore stdpifdata.maxmem?
                              MEMEMS_DEFAULT,
                              MEMEMS_MAX,
                              MEMXMS_DEFAULT,
                              MEMXMS_MAX,
};

PROPKBD kbdDefault          ={KBD_DEFAULT,
                              KBDINIT_DEFAULT,
                              KBDALTDELAY_DEFAULT,
                              KBDALTPASTEDELAY_DEFAULT,
                              KBDPASTEDELAY_DEFAULT,
                              KBDPASTEFULLDELAY_DEFAULT,
                              KBDPASTETIMEOUT_DEFAULT,
                              KBDPASTESKIP_DEFAULT,
                              KBDPASTECRSKIP_DEFAULT,
};

PROPMSE mseDefault          ={MSE_DEFAULT,
                              MSEINIT_DEFAULT,
};

PROPENV envDefault          ={ENV_DEFAULT,
                              ENVINIT_DEFAULT,
                              "",
                              ENVSIZE_DEFAULT,
                              ENVDPMI_DEFAULT,
};

WORD    flWinDefault        = WIN_DEFAULT;

/*
 * Default face name to use for Raster fonts.  Currently, this is
 * just a hard-coded value (ie, not maintained in any INI file).
 */
CHAR    szRasterFaceName[LF_FACESIZE] = "Terminal";


/*
 * Default face name to use for TrueType fonts.  It must be a monospace
 * font, and it must be a font that everyone is guaranteed to have.  Currently,
 * this can be changed by setting TTFont in [NonWindowsApp] in SYSTEM.INI.
 */
// now this is initialized with string resource. The 2nd element will get
// the native typeface for the bilingual dos prompt
CHAR    szTTFaceName[2][LF_FACESIZE] = {"Lucida Console", "Courier New"};

const TCHAR szAltKeyDelay        [] = TEXT("AltKeyDelay");
const TCHAR szAltPasteDelay      [] = TEXT("AltPasteDelay");
const TCHAR szKeyPasteDelay      [] = TEXT("KeyPasteDelay");
const TCHAR szKeyBufferDelay     [] = TEXT("KeyBufferDelay");
const TCHAR szKeyPasteTimeout    [] = TEXT("KeyPasteTimeout");
const TCHAR szKeyPasteSkipCount  [] = TEXT("KeyPasteSkipCount");
const TCHAR szKeyPasteCRSkipCount[] = TEXT("KeyPasteCRSkipCount");
const TCHAR szMouseInDosBox      [] = TEXT("MouseInDosBox");
const TCHAR szDisablePositionSave[] = TEXT("DisablePositionSave");
const TCHAR szDOSPromptExitInst  [] = TEXT("DOSPromptExitInstruc");
const TCHAR szCommandEnvSize     [] = TEXT("CommandEnvSize");
const TCHAR szScreenLines        [] = TEXT("ScreenLines");

const INIDATA aINIData[] = {
    {sz386EnhSection,   szAltKeyDelay,          &kbdDefault.msAltDelay,      INIDATA_FIXEDPOINT},
    {sz386EnhSection,   szAltPasteDelay,        &kbdDefault.msAltPasteDelay, INIDATA_FIXEDPOINT},
    {sz386EnhSection,   szKeyPasteDelay,        &kbdDefault.msPasteDelay,    INIDATA_FIXEDPOINT},
    {sz386EnhSection,   szKeyBufferDelay,       &kbdDefault.msPasteFullDelay,INIDATA_FIXEDPOINT},
    {sz386EnhSection,   szKeyPasteTimeout,      &kbdDefault.msPasteTimeout,  INIDATA_FIXEDPOINT},
    {sz386EnhSection,   szKeyPasteSkipCount,    &kbdDefault.cPasteSkip,      INIDATA_DECINT},
    {sz386EnhSection,   szKeyPasteCRSkipCount,  &kbdDefault.cPasteCRSkip,    INIDATA_DECINT},
    {szNonWinSection,   szMouseInDosBox,        &mseDefault.flMse,           INIDATA_BOOLEAN,  MSE_WINDOWENABLE},
    {szNonWinSection,   szDisablePositionSave,  &flWinDefault,               INIDATA_BOOLEAN | INIDATA_INVERT,  WIN_SAVESETTINGS},
#ifdef ENVINIT_INSTRUCTIONS
    {sz386EnhSection,   szDOSPromptExitInst,    &envDefault.flEnvInit,       INIDATA_BOOLEAN,  ENVINIT_INSTRUCTIONS},
#endif
    {szNonWinSection,   szCommandEnvSize,       &envDefault.cbEnvironment,   INIDATA_DECINT},
    {szNonWinSection,   szScreenLines,          &vidDefault.cScreenLines,    INIDATA_DECINT},
};

/**************************************************************************
 *
 *  OVERVIEW OF INI FILE USAGE
 *
 *
 *  SYSTEM.INI
 *
 *  [386Enh]
 *
 *  WOAFont=<fon filename>
 *
 *  Status:     Public
 *  Default:    dosapp.fon
 *  Purpose:
 *
 *      This setting allows the user to specify which Terminal font
 *      file should be loaded when DOS box is started.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *
 *  [NonWindowsApp]
 *
 *  DisablePositionSave=<Boolean>
 *
 *  Status:     Public
 *  Default:    0 (FALSE)
 *  Purpose:
 *
 *      When FALSE, the position and font used in a non-Windows
 *      application is saved in the application's PIF file when
 *      you exit the application.  When TRUE, the position, fonts, and
 *      toolbar state of a non-Windows application whose settings
 *      have not been previously saved in the DOSAPP.INI file will
 *      not be saved.
 *
 *      If enabled, the setting can be overridden for each
 *      non-Windows application by selecting the Save Settings On
 *      Exit check box in the Font dialog box.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *  Compatibility notes:
 *
 *      In Windows 3.x, the "position save" (and font) information was
 *      saved in DOSAPP.INI, and although we will still read DOSAPP.INI
 *      in the absence of any information in the PIF file, we only *write*
 *      settings back to the PIF file.  DOSAPP.INI should be considered
 *      obsolete.
 *
 *
 *  TTFont=<fontname>
 *
 *  Status:     ?
 *  Default:    Courier New     // FEATURE -- this should be a TT OEM font
 *  Purpose:
 *
 *      This setting allows the user to specify which TrueType font
 *      will be used in a DOS box.  It must be an OEM font.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *
 *  TTInitialSizes=<i1 i2 i3 i4 ... i16>
 *
 *  Status:     ?
 *  Default:    4 5 6 7 8 9 10 11 12 14 16 18 20 22 36 72
 *  Purpose:
 *
 *      This setting allows the user to specify which font sizes
 *      WinOldAp initially builds for the TrueType fonts in a DOS
 *      application window.
 *
 *      At most 16 font sizes can be requested.
 *
 *      Note that this INI entry is consulted only the first time
 *      Windows is restarted after changing video drivers or fonts.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *
 *  TTHeuristics=<i1 i2 i3 i4 i5 i6 i7 i8 i9>
 *
 *  Status:     Public
 *  Default:    5000 1000 0 1000 5000 1000 0 1000 1
 *  Purpose:
 *
 *      These integers control the way Windows chooses the font to
 *      display for DOS applications running inside a window if you
 *      have chosen "Auto" as the font size.
 *
 *      The parameters are named as follows:
 *
 *          i1=XOvershootInitial
 *          i2=XOvershootScale
 *          i3=XShortfallInitial
 *          i4=XShortfallScale
 *          i5=YOvershootInitial
 *          i6=YOvershootScale
 *          i7=YShortfallInitial
 *          i8=YShortfallScale
 *          i9=TrueTypePenalty
 *
 *      Each penalty value may not exceed 5000.
 *
 *      When Windows needs to select a font for use in a DOS
 *      application's window, it goes through the list of font
 *      sizes available and computes the "penalty" associated
 *      with using that font.  Windows then selects the font with
 *      the smallest penalty.
 *
 *      The horizontal penalty is computed as follows:
 *
 *          Let dxActual = <actual window width>
 *          Let dxDesired = <font width> * <characters per line>
 *
 *          If dxActual = dxDesired:
 *              xPenalty = 0
 *          If dxActual < dxDesired:
 *              Let Ratio = 1 - dxDesired / dxActual
 *              xPenalty = XOvershootInitial + Ratio * XOvershootScale
 *          If dxActual > dxDesired:
 *              Let Ratio = 1 - dxActual / dxDesired
 *              xPenalty = XShortfallInitial + Ratio * XShortfallScale
 *
 *      The vertical penalty is computed similarly.
 *
 *      Note that the Ratio is always a fraction between 0 and 1.
 *
 *      The penalty associated with a font is the sum of the vertical
 *      and horizontal penalties, plus the TrueTypePenalty if the font
 *      is a TrueType font.
 *
 *      The default value of 1 for the TrueTypePenalty means that,
 *      all other things being equal, Windows will select a raster
 *      font in preference to a TrueType font.  You can set this
 *      value to -1 if you wish the opposite preference.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *  Internals:
 *
 *      Even though floating point appears in the computations,
 *      everything is really done in integer arithmetic.
 *
 *      Pixels are NEVER MENTIONED anywhere in the penalty computations.
 *      (All pixel values are divided by other pixel values, so that
 *      we get a dimensionless number as a result.)
 *      This keeps us independent of the display resolution as well
 *      as the display aspect ratio.
 *
 *      Since the stretch and shrink are taken as fractions of the
 *      larger dimension, this keeps us from penalizing large
 *      differences by too much.  This is important because there
 *      isn't much visible difference between being ten times too
 *      big and being eleven times too big, but there is a big
 *      difference between being just right and being twice as big.
 *
 *      We must be careful not to let the maximum possible penalty
 *      exceed 32767.  This is done by making sure that each
 *      dimension cannot produce a penalty of greater than 10000
 *      (5000+5000), and that the TrueTypePenalty is at most 5000.
 *      This makes the maximum possible penalty 25000.
 *      This range checking is done by FontSelInit.
 *
 *
 *  TTNonAspectMin=<x y>
 *
 *  Status:     Public
 *  Default:    3 3
 *  Purpose:
 *
 *      These integers control the minimum width and height font that
 *      Windows will attempt to create automatically in response to a
 *      resize operation when TrueType fonts in DOS boxes are enabled
 *      and the "Auto" font size is selected.
 *
 *      These values prevent Windows from creating visually useless
 *      fonts like 10 x 1 or 1 x 10.  The default values prevent Windows
 *      from trying to create X x Y fonts if X < 3 or Y < 3.
 *
 *      TTNonAspectMin is not consulted if the font is being created at
 *      its default aspect ratio.  In other words, Windows will create,
 *      for example, a 1 x 3 font, if 1 x 3 is the standard aspect ratio
 *      for a 3-pixel-high font.
 *
 *      To permit all aspect ratios, set the values to "0 0".
 *
 *      To forbid all aspect ratios except for the standard aspect ratio,
 *      set the values to "-1 -1".
 *
 *  [TTFontDimenCache]
 *
 *  dxWidthRequested dyHeightRequested=dxWidthActual dyWidthActual
 *
 *  Status:     Private
 *  Default:    Null
 *  Purpose:
 *
 *      The [FontDimenCache] section contains information about
 *      TrueType font sizes that have been created.  Each entry
 *      has as the keyname the width and height that were passed
 *      to CreateFont and has as the value the width and height of
 *      the font that was actually created.
 *
 *  Internals:
 *
 *      Inspected by AddTrueTypeFontsToFontList.
 *      Set by AddOneNewTrueTypeFontToFontList.
 *
 *
 **************************************************************************
 *
 *  DOSAPP.INI (obsolete, supported on a read-only basis)
 *
 *  [Dos Applications]
 *
 *  C:\FULL\PATH\TO\EXE\COM\BAT\OR.PIF=<wFlags wFontWidth wFontHeight
 *          wWinWidth wWinHeight length flags showCmd ptMinPositionX
 *          ptMinPositionY ptMaxPositionX ptMaxPositionY
 *          rcNormalLeft rcNormalTop rcNormalRight rcNormalBottom>
 *
 *  Status:     Private
 *  Purpose:
 *
 *      These values are used to restore a DOS application's window
 *      to the state it was in when the DOS app last exited normally.
 *
 *      The values are taken directly from the INIINFO structure, qv.
 *
 *      The values of ptMinPositionX and ptMinPositionY are always -1,
 *      since we do not try to preserve the icon position.
 *
 *      If wFontHeight has the high bit set, then the font that
 *      should be used is a TrueType font.
 *
 *      If wFontWidth = 1 and wFontHeight = -1, then
 *      Auto-font-selection is active.
 *
 *  Compatibility notes:
 *
 *      In Windows 3.x, the "position save" (and font) information was
 *      saved in DOSAPP.INI, and although we will still read DOSAPP.INI
 *      in the absence of any information in the PIF file, we only *write*
 *      settings back to the PIF file.  DOSAPP.INI should be considered
 *      obsolete.
 *
 *
 **************************************************************************
 *
 * THE NEXT INI VAR IS NOT IMPLEMENTED BUT SHOULD BE
 *
 **************************************************************************
 *
 *  SYSTEM.INI
 *
 *  [NonWindowsApp]
 *
 *  TTFontTolerance=<i>
 *
 *  Status:     Public
 *  Default:    200
 *  Purpose:
 *
 *      This setting indicates how large a penalty (see TTHeuristics)
 *      Windows should tolerate before trying to synthesize new font
 *      sizes from TrueType fonts.
 *
 *      Decreasing this value will result in a tighter fit of the
 *      Windows-selected font to the actual window size, but at a
 *      cost in speed and memory.
 *
 *  To change:
 *
 *      Use Notepad to edit the SYSTEM.INI file.
 *
 *
 *  Internals:
 *
 *      Inspected by ChooseBestFont, if implemented.
 *
 **************************************************************************/



void PifMgrDLL_Init()
{
    static BOOL fInit = FALSE;
    if (!fInit)
    {
        LoadString(g_hinst, IDS_PIF_NONE, g_szNone, ARRAYSIZE(g_szNone));
        LoadString(g_hinst, IDS_AUTONORMAL, g_szAuto, ARRAYSIZE(g_szAuto));
        LoadGlobalFontData();
        fInit = TRUE;
    }
}

void PifMgrDLL_Term()
{
    FreeGlobalFontData();
}


/** GetPIFDir - Form default PIF directory name + name of given file
 *
 * INPUT
 *  None
 *
 * OUTPUT
 *  None
 */

void GetPIFDir(LPTSTR pszName)
{
    int i;
    static const TCHAR szBackslashPIF[] = TEXT("\\PIF");
    FunctionName(GetPIFDir);

    if (!(fbInit & INIT_PIFDIR)) {

        // Set up g_szPIFDir, less space for a filename, less space for \PIF

        i = ARRAYSIZE(g_szPIFDir)-lstrlen(pszName)-ARRAYSIZE(szBackslashPIF);
        if (i <= 0)                         // sanity check
            return;

        GetWindowsDirectory(g_szPIFDir, i);
        iPIFName = lstrlen(g_szPIFDir);
        if (StrRChr(g_szPIFDir, NULL, TEXT('\\')) == &g_szPIFDir[iPIFName-1])
            iPIFName--;
        iWinName = iPIFName;

        lstrcpy(g_szPIFDir+iPIFName, szBackslashPIF);
        iPIFName += ARRAYSIZE(szBackslashPIF)-1;

        i = (int)GetFileAttributes(g_szPIFDir);

        if (i == -1) {

            // It didn't exist, so try to create it (returns TRUE if success)

            i = CreateDirectory(g_szPIFDir, NULL);
            if (i)
                SetFileAttributes(g_szPIFDir, FILE_ATTRIBUTE_HIDDEN);
        }
        else if (i & FILE_ATTRIBUTE_DIRECTORY)
            i = TRUE;                       // directory already exists, cool!
        else
            i = FALSE;                      // some sort of file is in the way...

        if (i) {
            g_szPIFDir[iPIFName++] = TEXT('\\');    // append the slash we'll need
                                            // to separate future filenames (the
                                            // space after is already zero-init'ed)
        }
        else                                // we'll just have to use the Windows dir
            iPIFName -= ARRAYSIZE(szBackslashPIF)-2;

        fbInit |= INIT_PIFDIR;
    }

    // Now initialize g_szPIFDir with the name of the file we're processing

    if (pszName)
        lstrcpyn(g_szPIFDir+iPIFName, pszName, ARRAYSIZE(g_szPIFDir)-iPIFName);
}

/** GetINIData - Read WIN.INI/SYSTEM.INI/DOSAPP.INI for default settings
 *
 * INPUT
 *  Nothing
 *
 * OUTPUT
 *  Nothing; global defaults (re)set
 *
 * NOTES
 *  We only do this work now if GetPIFData couldn't open a PIF file, or
 *  could but it contained no enhanced section.  And we never do it more than
 *  once per fresh load of this DLL.
 */

void GetINIData()
{
    int t;
    const INIDATA *pid;
    LPCTSTR lpsz;
    DWORD dwRet;
    TCHAR szTemp[MAX_PATH];
    FunctionName(GetINIData);

    if (fbInit & INIT_INIDATA)          // if already done
        return;                         // then go away

    for (pid=aINIData; pid-aINIData < ARRAYSIZE(aINIData); pid++) {

        t = *(INT UNALIGNED *)pid->pValue;
        if (pid->iFlags & (INIDATA_DECINT | INIDATA_BOOLEAN)) {

            if (pid->iFlags & INIDATA_BOOLEAN) {
                t &= pid->iMask;
                if (pid->iFlags & INIDATA_INVERT)
                    t ^= pid->iMask;
            }
            t = GetPrivateProfileInt(pid->pszSection,
                                     pid->pszKey,
                                     t,
                                     szSystemINI);
            if (pid->iFlags & INIDATA_BOOLEAN) {
                if (t)
                    t = pid->iMask;
                if (pid->iFlags & INIDATA_INVERT)
                    t ^= pid->iMask;
                t |= *(INT UNALIGNED *)pid->pValue & ~pid->iMask;
            }
            *(INT UNALIGNED *)pid->pValue = t;
        }
        else
        if (pid->iFlags & INIDATA_FIXEDPOINT) {
            wsprintf(szTemp, szDotPercent03d, t);
            GetPrivateProfileString(pid->pszSection,
                                    pid->pszKey,
                                    szTemp,
                                    szTemp,
                                    ARRAYSIZE(szTemp),
                                    szSystemINI);
            *(INT UNALIGNED *)pid->pValue = StrToInt(szTemp+1);
        }
        else
            ASSERTFAIL();
    }

    //
    // Locate COMSPEC once and for all.
    //
    dwRet = GetEnvironmentVariable(TEXT("COMSPEC"), szTemp, ARRAYSIZE(szTemp));
    if (dwRet < ARRAYSIZE(szTemp) && dwRet > 0)
    {
        lpsz = StrRChr(szTemp, NULL, TEXT('\\'));
        if (lpsz) {
            lstrcpyn(szComspec, lpsz+1, ARRAYSIZE(szComspec));
        }
    }

    fbInit |= INIT_INIDATA;
}

/** InitProperties - initialize new property structure
 *
 * INPUT
 *  ppl -> property
 *  fLocked == TRUE to return data locked, FALSE unlocked
 *
 * OUTPUT
 *  Nothing (if successful, ppl->hPIFData will become non-zero)
 */

void InitProperties(PPROPLINK ppl, BOOL fLocked)
{
    LPSTDPIF lpstd;
    LPW386PIF30 lp386 = NULL;
    CHAR achPathName[ARRAYSIZE(ppl->szPathName)];
    BYTE behavior = 0;
    FunctionName(InitProperties);

    GetINIData();       // make sure we have all the right defaults

    if (ResizePIFData(ppl, sizeof(STDPIF)) != -1) {

        // We're no longer called *only* after a fresh ZERO'd HeapAlloc
        // by ResizePIFData.  We could be getting called because PifMgr_OpenProperties
        // was told to punt on an ambiguous PIF and create new settings.
        // Hence, we always zero-init the buffer ourselves now.

        BZero(ppl->lpPIFData, ppl->cbPIFData);

        lpstd = (LPSTDPIF)ppl->lpPIFData;
        lpstd->id = 0x78;
        PifMgr_WCtoMBPath( ppl->szPathName, achPathName, ARRAYSIZE(achPathName) );
        lstrcpyncharA(lpstd->appname, achPathName+ppl->iFileName, ARRAYSIZE(lpstd->appname), '.');
        CharToOemA(lpstd->appname, lpstd->appname);

        // NOTE: When 3.x Setup creates PIF files, it sets maxmem to 640;
        // that's typically what memDefault.wMaxLow will be too....

        lpstd->minmem = memDefault.wMinLow;
        lpstd->maxmem = (WORD) GetProfileInt(apszAppType[APPTYPE_PIF]+1, szMemory, memDefault.wMaxLow);
        lstrcpynA(lpstd->startfile, achPathName, ARRAYSIZE(lpstd->startfile));
        CharToOemA(lpstd->startfile, lpstd->startfile);

        //
        // New for 4.0:  fDestroy (close on exit) is disabled by default
        // for most apps, but is enabled by default for COMSPEC.
        //
        lpstd->MSflags = 0;
        if (!lstrcmpi(ppl->szPathName+ppl->iFileName, szComspec)) {
            lpstd->MSflags = fDestroy;
        }

        // Initialize various goofy non-zero stuff just to make it
        // look like a backward-compatible PIF file -- not that we use
        // or particularly care about any of it

        // NOTE: When 3.x Setup creates PIF files, it sets screen to 0x7F

        lpstd->cPages = 1;
        lpstd->highVector = 0xFF;
        lpstd->rows = 25;
        lpstd->cols = 80;
        lpstd->sysmem = 0x0007;

        // fFullScreen is no longer default, so only if an explicit
        // COMPATIBLE=FALSE exists in the PIF section of WIN.INI will
        // we set fScreen in behavior and fFullScreen in PfW386Flags
        // Similarly, fDestroy is no longer default, but we'll go
        // back to the old way if the switch tells us to.

        if (!GetProfileInt(apszAppType[APPTYPE_PIF]+1, szComp, TRUE)) {
            lpstd->behavior = behavior = fScreen;
            lpstd->MSflags = fDestroy;
        }

        if (ppl->ckbMem != -1 && ppl->ckbMem != 1)
            lpstd->minmem = lpstd->maxmem = (WORD) ppl->ckbMem;


        if (AddGroupData(ppl, szW386HDRSIG30, NULL, sizeof(W386PIF30))) {
            if (NULL != (lp386 = GetGroupData(ppl, szW386HDRSIG30, NULL, NULL))) {
                lp386->PfW386minmem = lpstd->minmem;
                lp386->PfW386maxmem = lpstd->maxmem;
                lp386->PfFPriority = TSKFGND_OLD_DEFAULT;
                lp386->PfBPriority = TSKBGND_OLD_DEFAULT;
                lp386->PfMinEMMK = memDefault.wMinEMS;
                lp386->PfMaxEMMK = memDefault.wMaxEMS;
                lp386->PfMinXmsK = memDefault.wMinXMS;
                lp386->PfMaxXmsK = memDefault.wMaxXMS;
                lp386->PfW386Flags = fBackground + fPollingDetect + fINT16Paste;
                if (behavior & fScreen)
                    lp386->PfW386Flags |= fFullScreen;
                lp386->PfW386Flags2 = fVidTxtEmulate + fVidNoTrpTxt + fVidNoTrpLRGrfx + fVidNoTrpHRGrfx + fVidTextMd;
            }
        }
        VERIFYTRUE(AddEnhancedData(ppl, lp386));
        if (AddGroupData(ppl, szWNTHDRSIG31, NULL, sizeof(WNTPIF31))) {
            LPWNTPIF31 lpnt31;

            if (NULL != (lpnt31 = GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL))) {
                lstrcpyA( lpnt31->nt31Prop.achConfigFile, NT_CONFIG_FILE );
                lstrcpyA( lpnt31->nt31Prop.achAutoexecFile, NT_AUTOEXEC_FILE );
            }
        }
        VERIFYTRUE(AddGroupData(ppl, szWNTHDRSIG40, NULL, sizeof(WNTPIF40)));

        // Can't be dirty anymore, 'cause we just set everything to defaults

        ppl->flProp &= ~PROP_DIRTY;

        if (!fLocked)
            ppl->cLocks--;
    }
    else
        ASSERTFAIL();
}


/** OpenPIFFile - Wrapper around CreateFile for opening PIF files
 *
 *  The wrapper handles the following things:
 *
 *      Passing the proper access and sharing flags to CreateFile.
 *      Setting pof->nErrCode = 0 on success.
 *      Converting ERROR_PATH_NOT_FOUND to ERROR_FILE_NOT_FOUND.
 *
 * INPUT
 *
 *  pszFile -> name of file to attempt to open
 *  pof -> PIFOFSTRUCT to fill in
 *
 *  OUTPUT
 *
 *  Same return code as CreateFile.
 *
 */

HANDLE OpenPIFFile(LPCTSTR pszFile, LPPIFOFSTRUCT pof)
{
    HANDLE hf;
    TCHAR pszFullFile[ MAX_PATH ];
    LPTSTR pszTheFile;
    DWORD dwRet;

    //
    // CreateFile does not search the path, so do that first, then
    // give CreateFile a fully qualified file name to open...
    //

    dwRet = SearchPath( NULL,
                        pszFile,
                        NULL,
                        ARRAYSIZE(pszFullFile),
                        pszFullFile,
                        &pszTheFile
                       );

    if ((dwRet==0) || (dwRet > ARRAYSIZE(pszFullFile)))
    {
        pszTheFile = (LPTSTR)pszFile;
    }
    else
    {
        pszTheFile = pszFullFile;
    }

    hf = CreateFile( pszTheFile,
                     GENERIC_READ,
                     FILE_SHARE_READ,
                     NULL,
                     OPEN_EXISTING,
                     FILE_ATTRIBUTE_NORMAL,
                     NULL );


    if (hf == INVALID_HANDLE_VALUE)
    {
        pof->nErrCode = GetLastError();
        if (pof->nErrCode == ERROR_PATH_NOT_FOUND)
            pof->nErrCode = ERROR_FILE_NOT_FOUND;
    }
    else
    {
        LPTSTR lpDummy;

        //
        //  NOTE:  Special hack for creating shortcuts.  If the PIF file
        //  that we find is 0 bytes long, pretend we did not find one at all.
        //  This is because appwiz renames a 0 length file from "New shortcut.lnk"
        //  to "appname.pif" and we end up finding it.  We'll ignore this file.
        //

        if (SetFilePointer( hf, 0, NULL, FILE_END) == 0)
        {
            CloseHandle( hf );
            hf = INVALID_HANDLE_VALUE;
            pof->nErrCode = ERROR_FILE_NOT_FOUND;
        }
        else
        {
            LPCTSTR pszNewFile;
            TCHAR szTemp[ ARRAYSIZE(pof->szPathName) ];

            SetFilePointer( hf, 0, NULL, FILE_BEGIN );
            pof->nErrCode = ERROR_SUCCESS;

            // In some cases, people pass in two pointers to the same
            // buffer.  This will hose GetFullPathName, so if they
            // are the same, then make a copy before calling GetFullPathName.
            if (pszTheFile==pof->szPathName) {
                FillMemory( szTemp, sizeof(szTemp), 0 );
                lstrcpy( szTemp, pszTheFile );
                pszNewFile = szTemp;
            }
            else
            {
                pszNewFile = pszTheFile;
            }
            GetFullPathName( pszNewFile, ARRAYSIZE(pof->szPathName),
                             pof->szPathName, &lpDummy );
        }
    }

    return hf;
}


/** PifMgr_OpenProperties - return handle to property info for application
 *
 * INPUT
 *  lpszApp -> name of application
 *  lpszPIF -> name of PIF file to use/create
 *  hInf = Inf handle, 0 if none, -1 to inhibit Inf processing
 *  flOpt = OPENPROPS_RAWIO to allow raw file updates; otherwise, 0
 *
 * OUTPUT
 *  handle to properties, FALSE if could not be opened, or out of memory
 *
 * REMARKS
 *  This should not be thought of as a function that opens a file somewhere
 *  on the disk (although that's usually the effect), but rather as an
 *  property structure allocator that is optionally initialized by disk data
 *  (currently, the file does not even remain open after this call).  So the
 *  main reason for failure in this function will be either a low memory
 *  condition *or* inability to open a specific PIF file.
 *
 *  The rules for PIF file searching are as follows:
 *
 *      If not a .PIF file:
 *          Search in current directory.
 *      Endif.
 *
 *      If path components were specified:
 *          Search in specified directory.
 *      Endif.
 *
 *      Search in PIF directory.
 *      Search the path.
 *
 *  Note that this differs from the Windows 3.1 PIF search algorithm, which
 *  was...
 *
 *      Search current directory.
 *      Search the path.
 *      Search in application directory.
 *
 *  This was a really bogus search order.  Fortunately, it seems that
 *  very few people relied on it.
 *
 *  Things to watch out for when dorking the PIF file search order:
 *
 *      Make sure editing PIF properties from the shell works.  (I.e.,
 *      if a full path to a PIF is given, then use it; don't search.)
 *
 *  Extra special thing to watch out for when dorking the PIF file
 *  search order:
 *
 *      MS Delta execs its child process as follows:
 *
 *          CreatePif("C:\DELTA\DELTABAT.PIF");
 *          SetCurrentDirectory("C:\RANDOM\PLACE");
 *          WinExec("C:\TMP\DELTABAT.BAT", SW_HIDE);
 *
 *      It expects the PIF search to pick up C:\DELTA\DELTABAT.PIF
 *      from the path, even though the WinExec supplied a full path.
 *
 */

HANDLE WINAPI PifMgr_OpenProperties(LPCTSTR lpszApp, LPCTSTR lpszPIF, UINT hInf, UINT flOpt)
{
    PPROPLINK ppl;
    LPTSTR pszExt;
    BOOL fError = FALSE;
    BOOL fFixedDisk = FALSE;
    BOOL fSearchInf = FALSE;
    BOOL fExplicitPIF = FALSE;
    PROPPRG prg;
    PROPNT40 nt40;
    LPTSTR pszName, pszFullName;
#ifdef DBCS
    PROPENV env;
#endif
    FunctionName(PifMgr_OpenProperties);
    // Allocate new prop

    if (!(ppl = (PPROPLINK)LocalAlloc(LPTR, sizeof(PROPLINK))))
        return 0;

    if (!(pszFullName = (LPTSTR)LocalAlloc(LPTR, MAXPATHNAME*sizeof(TCHAR)))) {
        EVAL(LocalFree(ppl) == NULL);
        return 0;
    }

    if ((HANDLE)ppl > g_offHighestPropLink) {
        g_offHighestPropLink = (HANDLE)ppl;

    }

    // Initialize the new prop

    ppl->ppl = ppl;
    ppl->ckbMem = -1;
    ppl->iSig = PROP_SIG;
    ppl->hPIF = INVALID_HANDLE_VALUE;
    if (flOpt & OPENPROPS_RAWIO)
        ppl->flProp |= PROP_RAWIO;

    #if (PRGINIT_INHIBITPIF != PROP_INHIBITPIF)
    #error PRGINIT_INIHIBITPIF and PROP_INHIBITPIF out of sync!
    #endif

    ppl->flProp |= (flOpt & PROP_INHIBITPIF);

    // Link into the global list

    if (NULL != (ppl->pplNext = g_pplHead))
        g_pplHead->pplPrev = ppl;
    g_pplHead = ppl;

    // Copy app name to both temp and perm buffers, and record location
    // of base filename, and extension if any, within the buffer

    lstrcpyn(pszFullName, lpszApp, MAXPATHNAME-4);
    lstrcpyn(ppl->szPathName, pszFullName, ARRAYSIZE(ppl->szPathName));

    if (NULL != (pszName = StrRChr(pszFullName, NULL, TEXT('\\'))) ||
        NULL != (pszName = StrRChr(pszFullName, NULL, TEXT(':'))))
        pszName++;
    else
        pszName = pszFullName;

    if (!(pszExt = StrRChr(pszName, NULL, TEXT('.'))))
        pszExt = pszFullName + lstrlen(pszFullName);

    ppl->iFileName = (UINT) (pszName - pszFullName);
    ppl->iFileExt = (UINT) (pszExt - pszFullName);

    // Check the application's file extension

    if (!*pszExt) {
        lstrcat(pszFullName, apszAppType[APPTYPE_PIF]);
    }
    else if (!lstrcmpi(pszExt, apszAppType[APPTYPE_EXE]) ||
             !lstrcmpi(pszExt, apszAppType[APPTYPE_COM]) ||
             !lstrcmpi(pszExt, apszAppType[APPTYPE_BAT])) {
//             !lstrcmpi(pszExt, apszAppType[APPTYPE_CMD])) {
        lstrcpy(pszExt, apszAppType[APPTYPE_PIF]);
    }
    else if (!lstrcmpi(pszExt, apszAppType[APPTYPE_PIF]))
        fExplicitPIF = TRUE;
    else {
        // Let's disallow random file extensions, since WinOldAp never
        // allowed them either
        goto Error;
    }

    // INFONLY means the caller just wants to search the INF, so ignore
    // any WIN.INI garbage and any PIFs laying around.  We still look for
    // _DEFAULT.PIF, since that code takes care of other important
    // initialization that needs to happen when no PIF was found at all.

    if (flOpt & OPENPROPS_INFONLY)
        goto FindDefault;

    // Backward compatibility requires that if the app is not a PIF,
    // then we must check the PIF section of WIN.INI for an entry matching
    // the base name of the app.  If the entry exists, then we have to skip
    // the PIF search, and pass the value of the entry to InitProperties,
    // which it uses to establish default memory requirements
    //
    // Also note that if IGNOREPIF is set, then ofPIF.szPathName is nothing
    // more than the name of the app that was given to PifMgr_OpenProperties;  this
    // may give us the opportunity to do something more intelligent later...

    if (!fExplicitPIF) {
        ppl->ckbMem = GetProfileInt(apszAppType[APPTYPE_PIF]+1, ppl->szPathName+ppl->iFileName, -1);
        if (ppl->ckbMem != -1) {
            ppl->flProp |= PROP_IGNOREPIF | PROP_SKIPPIF;
            lstrcpyn(ppl->ofPIF.szPathName, lpszApp, ARRAYSIZE(ppl->ofPIF.szPathName));
            goto IgnorePIF;     // entry exists, skip PIF file search
        }
    }

    //
    // Initialize default error return code.  Once we get a successful
    // open, it will be set to zero.
    //
    ppl->flProp |= PROP_NOCREATEPIF;
    ppl->ofPIF.nErrCode = ERROR_FILE_NOT_FOUND;

    //
    // We must search in the current directory if not given a path to a PIF.
    // We need to prefix `.\' to the filename so that OpenFile will not do
    // a path search.
    //
    if (!fExplicitPIF || pszName == pszFullName) {
        //
        // This relies on a feature of OpenFile, that it copies the input
        // buffer to a private buffer before stomping the output buffer,
        // thus permitting precisely the stunt we are pulling here, namely,
        // passing an input buffer equal to the output buffer.
        //
        *(LPDWORD)(ppl->ofPIF.szPathName) = 0x005C002E; /*dot backslash prefix */
        lstrcpyn( &ppl->ofPIF.szPathName[2],
                  pszName,
                  ARRAYSIZE(ppl->ofPIF.szPathName) - 2);
        ppl->hPIF = OpenPIFFile(ppl->ofPIF.szPathName, &ppl->ofPIF);
    }

    //
    // If we were given a path component, then look in that directory.
    // (The fact that we have a backslash or drive letter will suppress
    // the path search.)
    //
    if (pszName != pszFullName && ppl->ofPIF.nErrCode == ERROR_FILE_NOT_FOUND) {

        ppl->hPIF = OpenPIFFile(pszFullName, &ppl->ofPIF);

        // If we didn't find a PIF there, we'd probably still like to create
        // one there if the media is a fixed disk.  Network shares, CD-ROM
        // drives, and floppies are not good targets for PIF files in general.
        //
        // So, if the media is a fixed disk, set the fFixedDisk flag so that
        // we'll leave pszFullName alone.

        if (ppl->hPIF == INVALID_HANDLE_VALUE && pszFullName[1] == TEXT(':')) {
            TCHAR szTemp[4];

            lstrcpyn( szTemp, pszFullName, 4 );

            szTemp[3] = (TCHAR)0;

            if (GetDriveType(szTemp) == DRIVE_FIXED)
                    fFixedDisk++;
        }
    }

    // PERF: replace this PIF dir search with a registry search -JTP
    //
    // Failing that, let's look in the PIF directory.  Again, since we're
    // supplying a full pathname, OpenFile won't try to search the PATH again.

    if (ppl->ofPIF.nErrCode == ERROR_FILE_NOT_FOUND) {
        GetPIFDir(pszName);
        ppl->hPIF = OpenPIFFile(g_szPIFDir, &ppl->ofPIF);
        if (ppl->hPIF != INVALID_HANDLE_VALUE)
            ppl->flProp |= PROP_PIFDIR;
    }

    // If we're still in trouble, our last chance is to do a path
    // search.  This is an unconditional search, thanks to the
    // wonders of MS-Delta.

    if (ppl->ofPIF.nErrCode == ERROR_FILE_NOT_FOUND) {
        ppl->hPIF = OpenPIFFile(pszName, &ppl->ofPIF);
    }

    if (ppl->hPIF == INVALID_HANDLE_VALUE) {

        if (ppl->ofPIF.nErrCode != ERROR_FILE_NOT_FOUND || fExplicitPIF) {

            // Hmmm, file *may* exist, but it cannot be opened;  if it's a
            // strange error, or we were specifically told to open that file,
            // then return error

            goto Error;
        }

    FindDefault:

        fSearchInf = TRUE;
        ppl->flProp &= ~PROP_NOCREATEPIF;

        // Any files we find now are NOT really what we wanted, so save
        // the name we'd like to use in the future, in case we need to save
        // updated properties later.
        //
        // We must save the name now because we might stomp g_szPIFDir while
        // searching for the _default.pif.  Furthermore, we must save it in
        // the buffer we HeapAlloc'ed (pszFullName) temporarily, because
        // the following calls to OpenPIFFile can still stomp on szPathName
        // in our OpenFile structure (ofPIF.szPathName).

        GetPIFDir(pszName);
        if (!fFixedDisk)                        // save desired name in
            lstrcpy(pszFullName, g_szPIFDir);     // temp buffer (pszFullName)

        //
        // Try to locate the _default.pif.
        //

        if (fTryDefaultPif()) {

            if (!fDefaultPifFound()) {          // Must search for it

                // First try PIFDir

                lstrcpy(g_szPIFDir+iPIFName, g_szDefaultPIF);
                ppl->hPIF = OpenPIFFile(g_szPIFDir, &ppl->ofPIF);

                if (ppl->ofPIF.nErrCode == ERROR_FILE_NOT_FOUND) { // try PATH
                    ppl->hPIF = OpenPIFFile(g_szDefaultPIF, &ppl->ofPIF);
                }

            } else {                            // Look in cached path

                // We've already found it once, so just open it

                ppl->hPIF = OpenPIFFile(g_szDefaultPIF, &ppl->ofPIF);
            }
        }

        if (ppl->hPIF != INVALID_HANDLE_VALUE) {

            ppl->flProp |= PROP_DEFAULTPIF;

            // Save the fully-qualified pathname of the default PIF file,
            // so that subsequent OpenFile() calls will be faster (note that
            // we don't specify OF_SEARCH on that particular call)

            lstrcpy(g_szDefaultPIF, ppl->ofPIF.szPathName);
        }
        else {

            // Not only could we not open any sort of PIF, we also need to
            // tell GetPIFData to not bother trying to open the file itself
            // (since it is unlikely someone created one in this short time)

            ppl->flProp |= PROP_NOPIF | PROP_SKIPPIF;

            if (ppl->ofPIF.nErrCode == ERROR_FILE_NOT_FOUND)
                g_szDefaultPIF[0] = 0;            // Invalidate cache.
        }

        // NOW we can set ppl->ofPIF.szPathName to the filename we REALLY
        // wanted, since we're done with all the calls to OpenPIFFile.

        lstrcpy(ppl->ofPIF.szPathName, pszFullName);
    }

    // Initialize the properties by PIF if we have one, by hand if not

  IgnorePIF:

    // We don't need to check the return code from GetPIFData() here,
    // because we validate hPIFData below anyway.  Please also note that
    // this GetPIFData call uses the handle we supplied (if any), and closes
    // it for us when it's done.  Furthermore, if we didn't supply a handle,
    // then we should have set PROP_SKIPPIF, so that GetPIFData won't try to
    // open anything (since we just tried!)

    GetPIFData(ppl, FALSE);

    // Now that the original file from which we obtained settings (if any) is
    // closed, we need to see if the caller wants us to create a new PIF file
    // using a specific name.  If so, force it to be created now.

    if (lpszPIF) {
        lstrcpy(ppl->ofPIF.szPathName, lpszPIF);
        ppl->flProp |= PROP_DIRTY;
        ppl->flProp &= ~PROP_NOCREATEPIF;
        fError = !FlushPIFData(ppl, FALSE);
    }

    // Apply INF data to the PIF data we just retrieved, as appropriate,
    // as long as it's an app file and not a PIF file (and if, in the case of
    // creating a specific PIF, we were actually able to create one).

    if (!fError && !fExplicitPIF && (hInf != -1)) {

        if (PifMgr_GetProperties(ppl, MAKELP(0,GROUP_PRG),
                            &prg, sizeof(prg), GETPROPS_NONE)) {

            // In the PRGINIT_AMBIGUOUSPIF case, GetAppsInfData must
            // again look for a matching entry;  however, if the entry it
            // finds is the same as what we've already got (based on Other
            // File), then it will leave the PIF data alone (ie, it doesn't
            // reinitialize it, it doesn't call AppWiz to silently
            // reconfigure it, etc).

            if (fSearchInf || (prg.flPrgInit & PRGINIT_AMBIGUOUSPIF)) {

                if (PifMgr_GetProperties(ppl, MAKELP(0,GROUP_NT40),
                                &nt40, sizeof(nt40), GETPROPS_NONE)) {

                if (!GetAppsInfData(ppl, &prg, &nt40, (HINF)IntToPtr( hInf ), lpszApp, fFixedDisk, flOpt)) {

                    // When GetAppsInfData fails and the PIF is ambiguous, then
                    // we need to restart the PIF search process at the point where
                    // it searches for _DEFAULT.PIF, so that the ambiguous PIF is
                    // effectively ignored now.

                    // Also, we avoid the ugly possibility of getting to this
                    // point again and infinitely jumping back FindDefault, by
                    // only jumping if fSearchInf was FALSE.  FindDefault sets
                    // it to TRUE.

                    if (!fSearchInf && (prg.flPrgInit & PRGINIT_AMBIGUOUSPIF)) {
                        goto FindDefault;
                    }
#ifdef DBCS
                    if (GetSystemDefaultLangID() == 0x0411) {
                        ZeroMemory(&env, sizeof(env));
                        lstrcpyA(env.achBatchFile, ImeBatchFile);
                        PifMgr_SetProperties(ppl, MAKELP(0,GROUP_ENV),
                                             &env, sizeof(env), SETPROPS_NONE);
                    }
#endif
                }
                }
            }
        }
    }

  Error:
    LocalFree(pszFullName);

    if (fError || !ppl->lpPIFData) {
        PifMgr_CloseProperties(ppl, 0);
        return 0;
    }

    // We should never leave PIFMGR with outstanding locks

    ASSERTTRUE(!ppl->cLocks);

    return ppl;
}


/** PifMgr_GetProperties - get property info by name
 *
 * INPUT
 *  hProps = handle to properties
 *  lpszGroup -> property group; may be one of the following:
 *      "WINDOWS 286 3.0"
 *      "WINDOWS 386 3.0"
 *      "WINDOWS VMM 4.0"
 *      "WINDOWS NT  3.1"
 *      "WINDOWS NT  4.0"
 *    or any other group name that is the name of a valid PIF extension;
 *    if NULL, then cbProps is a 0-based index of a named group, and lpProps
 *    must point to a 16-byte buffer to receive the name of the group (this
 *    enables the caller to enumerate the names of all the named groups)
 *  lpProps -> property group record to receive the data
 *  cbProps = size of property group record to get; if cbProps is zero
 *    and a named group is requested, lpProps is ignored, no data is copied,
 *    and the size of the group record is returned (this enables the caller
 *    to determine the size of a named group)
 *  flOpt = GETPROPS_RAWIO to perform raw file read (lpszGroup ignored)
 *
 *  Alternatively, if the high word (selector) of lpszGroup is 0, the low
 *  word must be a group ordinal (eg, GROUP_PRG, GROUP_TSK, etc)
 *
 * OUTPUT
 *  If the group is not found, or an error occurs, 0 is returned.
 *  Otherwise, the size of the group info transferred in bytes is returned.
 */

int WINAPI PifMgr_GetProperties(HANDLE hProps, LPCSTR lpszGroup, void *lpProps, int cbProps, UINT flOpt)
{
    int cb, i;
    void *lp;
    LPW386PIF30 lp386;
    LPWENHPIF40 lpenh;
    LPWNTPIF40 lpnt40;
    LPWNTPIF31 lpnt31;

    PPROPLINK ppl;
    FunctionName(PifMgr_GetProperties);

    cb = 0;

    if (!(ppl = ValidPropHandle(hProps)))
        return cb;

    // We should never enter PIFMGR with outstanding locks (we also call
    // here from *inside* PIFMGR, but none of those cases should require a
    // lock either)

    ASSERTTRUE(!ppl->cLocks);

    ppl->cLocks++;

    if (flOpt & GETPROPS_RAWIO) {
        if (ppl->flProp & PROP_RAWIO) {
            cb = min(ppl->cbPIFData, cbProps);
            hmemcpy(lpProps, ppl->lpPIFData, cb);
        }
        ppl->cLocks--;
        return cb;
    }

    if (!lpszGroup) {
        if (lpProps) {
            lp = GetGroupData(ppl, NULL, &cbProps, NULL);
            if (lp) {
                cb = cbProps;
                hmemcpy(lpProps, lp, PIFEXTSIGSIZE);
            }
        }
    }
    else if (IS_INTRESOURCE(lpszGroup) && lpProps) {

        // Special case: if GROUP_ICON, then do a nested call to
        // PifMgr_GetProperties to get GROUP_PRG data, then feed it to load
        // LoadPIFIcon, and finally return the hIcon, if any, to the user.

        if (LOWORD((DWORD_PTR) lpszGroup) == GROUP_ICON) {
            PPROPPRG pprg;
            PPROPNT40 pnt40 = (void *)LocalAlloc(LPTR, sizeof(PROPNT40));
            if ( pnt40 ) {
                pprg = (void *)LocalAlloc(LPTR, sizeof(PROPPRG));
                if (pprg) {
                    if ( PifMgr_GetProperties(ppl, MAKELP(0,GROUP_PRG), pprg, sizeof(PROPPRG), GETPROPS_NONE)
                          && PifMgr_GetProperties(ppl, MAKELP(0,GROUP_NT40), pnt40, sizeof(PROPNT40), GETPROPS_NONE) ) {
                        *(HICON *)lpProps = LoadPIFIcon(pprg, pnt40);
                        cb = 2;
                    }
                    EVAL(LocalFree(pprg) == NULL);
                }
                EVAL(LocalFree(pnt40) == NULL);
            }
        }
        else {
            lp386 = GetGroupData(ppl, szW386HDRSIG30, NULL, NULL);
            lpenh = GetGroupData(ppl, szWENHHDRSIG40, NULL, NULL);
            lpnt40 = GetGroupData(ppl, szWNTHDRSIG40, NULL, NULL);
            lpnt31  = GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL);

            //
            // Fix anything from down-level PIF files.  Since this
            // is the first revision of the WENHPIF40 format, we
            // don't have anything to worry about (yet).
            //
            // Don't muck with PIF files from the future!
            //
            if (lpenh && lpenh->wInternalRevision != WENHPIF40_VERSION) {
                lpenh->wInternalRevision = WENHPIF40_VERSION;
                ppl->flProp |= PROP_DIRTY;

                //
                //  Old (pre-M7) PIFs did not zero-initialize the reserved
                //  fields of PIF files, so zero them out now.
                //
                lpenh->tskProp.wReserved1 = 0;
                lpenh->tskProp.wReserved2 = 0;
                lpenh->tskProp.wReserved3 = 0;
                lpenh->tskProp.wReserved4 = 0;
                lpenh->vidProp.wReserved1 = 0;
                lpenh->vidProp.wReserved2 = 0;
                lpenh->vidProp.wReserved3 = 0;
                lpenh->envProp.wMaxDPMI = 0;

                // Turn off bits that have been deleted during the development
                // cycle.
                lpenh->envProp.flEnv = 0;
                lpenh->envProp.flEnvInit = 0;
                if (lp386)
                    lp386->PfW386Flags &= ~0x00400000;
            }
            // End of "Remove this after M8"

            // Zero the input buffer first, so that the Get* functions
            // need not initialize every byte to obtain consistent results

            BZero(lpProps, cbProps);

            // The GetData functions CANNOT rely on either lp386 or lpenh

            i = LOWORD((DWORD_PTR) lpszGroup)-1;
            if (i >= 0 && i < ARRAYSIZE(afnGetData) && cbProps >= acbData[i]) {
                void *aDataPtrs[NUM_DATA_PTRS];

                aDataPtrs[ LP386_INDEX ] = (LPVOID)lp386;
                aDataPtrs[ LPENH_INDEX ] = (LPVOID)lpenh;
                aDataPtrs[ LPNT40_INDEX ] = (LPVOID)lpnt40;
                aDataPtrs[ LPNT31_INDEX ] = (LPVOID)lpnt31;

                cb = (afnGetData[i])(ppl, aDataPtrs, lpProps, cbProps, flOpt );
            }
        }
    }
    else if (NULL != (lp = GetGroupData(ppl, lpszGroup, &cb, NULL))) {
        if (lpProps && cbProps != 0) {
            cb = min(cb, cbProps);
            hmemcpy(lpProps, lp, cb);
        }
    }
    ppl->cLocks--;

#ifdef EXTENDED_DATA_SUPPORT

    // Note that for GETPROPS_EXTENDED, both the normal and extended
    // sections are returned, and that the return code reflects the success
    // or failure of reading the normal portion only.  We return both because
    // that's the most convenient thing to do for the caller.

    if (flOpt & GETPROPS_EXTENDED) {
        if (ppl->hVM) {
            WORD wGroup = EXT_GROUP_QUERY;
            if (!HIWORD(lpszGroup) && LOWORD(lpszGroup) <= MAX_GROUP)
                wGroup |= LOWORD(lpszGroup);
            GetSetExtendedData(ppl->hVM, wGroup, lpszGroup, lpProps);
        }
    }
#endif

    // We should never leave PIFMGR with outstanding locks (we also call
    // here from *inside* PIFMGR, but none of those cases should require a
    // lock either)

    ASSERTTRUE(!ppl->cLocks);

    return cb;
}


/** PifMgr_SetProperties - set property info by name
 *
 * INPUT
 *  hProps = handle to properties
 *  lpszGroup -> property group; may be one of the following:
 *      "WINDOWS 286 3.0"
 *      "WINDOWS 386 3.0"
 *      "WINDOWS PIF.400"
 *    or any other group name that is the name of a valid PIF extension
 *  lpProps -> property group record to copy the data from
 *  cbProps = size of property group record to set;  if cbProps is
 *    zero and lpszGroup is a group name, the group will be removed
 *  flOpt = SETPROPS_RAWIO to perform raw file write (lpszGroup ignored)
 *          SETPROPS_CACHE to cache changes until properties are closed
 *
 *  Alternatively, if the high word (selector) of lpszGroup is 0, the low
 *  word must be a group ordinal (eg, GROUP_PRG, GROUP_TSK, etc)
 *
 * OUTPUT
 *  If the group is not found, or an error occurs, 0 is returned.
 *  Otherwise, the size of the group info transferred in bytes is returned.
 */

int WINAPI PifMgr_SetProperties(HANDLE hProps, LPCSTR lpszGroup, void *lpProps, int cbProps, UINT flOpt)
{
    void *p = NULL;
    void *lp = NULL;
    LPW386PIF30 lp386;
    LPWENHPIF40 lpenh;
    LPWNTPIF40 lpnt40;
    LPWNTPIF31 lpnt31;
    int i, cb = 0;
    PPROPLINK ppl;

    FunctionName(PifMgr_SetProperties);

    // Can't set a NULL name (nor set-by-index)--causes squirlly behavior in RemoveGroupData
    if (!lpProps || !lpszGroup)
        return 0;

    ppl = ValidPropHandle(hProps);
    if (!ppl)
        return 0;

    // We should never enter PIFMGR with outstanding locks (we also call
    // here from *inside* PIFMGR, but none of those cases should require a
    // lock either)

    ASSERTTRUE(!ppl->cLocks);

    if (flOpt & SETPROPS_RAWIO) {
        if (ppl->flProp & PROP_RAWIO) {
            ppl->cLocks++;
            cb = min(ppl->cbPIFData, cbProps);
            if (IsBufferDifferent(ppl->lpPIFData, lpProps, cb)) {
                hmemcpy(ppl->lpPIFData, lpProps, cb);
                ppl->flProp |= PROP_DIRTY;
            }
            if (cb < ppl->cbPIFData)
                ppl->flProp |= PROP_DIRTY | PROP_TRUNCATE;
            ppl->cbPIFData = cb;
            ppl->cLocks--;
        }
        return cb;
    }

#ifdef EXTENDED_DATA_SUPPORT

    // Note that, unlike GETPROPS_EXTENDED, SETPROPS_EXTENDED only updates
    // the extended section, and that the return code reflects the existence
    // of a VM only.  This is because there's a performance hit associated
    // with setting the normal portion, and because the caller generally only
    // wants to set one or the other.

    if (flOpt & SETPROPS_EXTENDED) {
        if (ppl->hVM) {
            WORD wGroup = EXT_GROUP_UPDATE;
            cb = cbProps;
            if (!HIWORD(lpszGroup) && LOWORD(lpszGroup) <= MAX_GROUP)
                wGroup |= LOWORD(lpszGroup);
            GetSetExtendedData(ppl->hVM, wGroup, lpszGroup, lpProps);
        }
        return cb;
    }
#endif

    // For named groups, if the group does NOT exist, or DOES but is
    // a different size, then we have to remove the old data, if any, and
    // then add the new.

    if (!IS_INTRESOURCE(lpszGroup)) {

        cb = PifMgr_GetProperties(hProps, lpszGroup, NULL, 0, GETPROPS_NONE);

        if (cb == 0 || cb != cbProps) {
            if (cb) {
                RemoveGroupData(ppl, lpszGroup);
                cb = 0;
            }
            if (cbProps) {
                if (AddGroupData(ppl, lpszGroup, lpProps, cbProps))
                    cb = cbProps;
            }
            goto done;
        }
    }

    if (cbProps) {
        if (!lpszGroup)
            return cb;

        p = (void *)LocalAlloc(LPTR, cbProps);
        if (!p)
            return cb;
    }

    cb = PifMgr_GetProperties(hProps, lpszGroup, p, cbProps, GETPROPS_NONE);

    // If the group to set DOES exist, and if the data given is
    // different, copy into the appropriate group(s) in the PIF data

    if (cb != 0) {
        cbProps = min(cb, cbProps);
        if (IsBufferDifferent(p, lpProps, cbProps)) {
            cb = 0;
            ppl->cLocks++;
            i = LOWORD((DWORD_PTR) lpszGroup)-1;
            if (!IS_INTRESOURCE(lpszGroup)) {
                lp = GetGroupData(ppl, lpszGroup, NULL, NULL);
                if (lp) {
                    cb = cbProps;
                    hmemcpy(lp, lpProps, cbProps);
                    ppl->flProp |= PROP_DIRTY;
                }
            }
            else if (i >= 0 && i < ARRAYSIZE(afnSetData) && cbProps >= acbData[i]) {

                // Insure that both 386 and enhanced sections of PIF
                // file are present.  There are some exceptions:  all
                // groups from GROUP_MSE on up do not use the 386 section,
                // and GROUP_MEM does not need the enh section....

                lp386 = GetGroupData(ppl, szW386HDRSIG30, NULL, NULL);
                if (i < GROUP_MSE-1 && !lp386) {
                    if (AddGroupData(ppl, szW386HDRSIG30, NULL, sizeof(W386PIF30))) {
                        lp386 = GetGroupData(ppl, szW386HDRSIG30, NULL, NULL);
                        if (!lp386) {
                            ASSERTFAIL();
                            cbProps = 0;    // indicate error
                        }
                    }
                }
                if (cbProps) {
                    lpenh = GetGroupData(ppl, szWENHHDRSIG40, NULL, NULL);
                    if (i != GROUP_MEM-1 && !lpenh) {
                        if (!(lpenh = AddEnhancedData(ppl, lp386))) {
                            ASSERTFAIL();
                            cbProps = 0;    // indicate error
                        }
                    }
                    lpnt40 = GetGroupData(ppl, szWNTHDRSIG40, NULL, NULL);
                    if (!lpnt40)
                    {
                        if (AddGroupData(ppl, szWNTHDRSIG40, NULL, sizeof(WNTPIF40)))
                        {
                            lpnt40 = GetGroupData(ppl, szWNTHDRSIG40, NULL, NULL);
                        }
                    }
                    ASSERT(lpnt40);

                    lpnt31 = GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL);
                    if (!lpnt31)
                    {
                        if (AddGroupData(ppl, szWNTHDRSIG31, NULL, sizeof(WNTPIF31)))
                        {
                            if (NULL != (lpnt31 = GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL))) {
                                lstrcpyA( lpnt31->nt31Prop.achConfigFile, NT_CONFIG_FILE );
                                lstrcpyA( lpnt31->nt31Prop.achAutoexecFile, NT_AUTOEXEC_FILE );
                            }
                        }
                    }
                    ASSERT(lpnt31);
                }
                if (cbProps)
                {

                    void *aDataPtrs[NUM_DATA_PTRS];

                    //
                    // We need to re-establish the pointers because any of
                    // the AddGroupData's could have moved the block (via
                    // a HeapReAlloc call), so do that now...
                    //

                    lp386 = GetGroupData( ppl, szW386HDRSIG30, NULL, NULL );
                    lpenh = GetGroupData( ppl, szWENHHDRSIG40, NULL, NULL );
                    lpnt40 = GetGroupData( ppl, szWNTHDRSIG40, NULL, NULL );
                    lpnt31 = GetGroupData( ppl, szWNTHDRSIG31, NULL, NULL );

                    aDataPtrs[ LP386_INDEX ] = (LPVOID)lp386;
                    aDataPtrs[ LPENH_INDEX ] = (LPVOID)lpenh;
                    aDataPtrs[ LPNT40_INDEX ] = (LPVOID)lpnt40;
                    aDataPtrs[ LPNT31_INDEX ] = (LPVOID)lpnt31;
                    cb = (afnSetData[i])(ppl, aDataPtrs, lpProps, cbProps, flOpt );
                }
            }
            ppl->cLocks--;
        }
    }
    EVAL(LocalFree(p) == NULL);

  done:
    if (!(flOpt & SETPROPS_CACHE))
        if (!FlushPIFData(ppl, FALSE))
            cb = 0;

    // We should never leave PIFMGR with outstanding locks (we also call
    // here from *inside* PIFMGR, but none of those cases should require a
    // lock either)

    ASSERTTRUE(!ppl->cLocks);

    return cb;
}


/** FlushProperties - flush (or discard) any cached property info
 *
 * INPUT
 *  hProps = handle to properties
 *  flOpt = FLUSHPROPS_DISCARD to abandon cached PIF data, otherwise flush it
 *
 * OUTPUT
 *  TRUE if successful, FALSE otherwise
 */

int WINAPI FlushProperties(HANDLE hProps, UINT flOpt)
{
    PPROPLINK ppl;
    FunctionName(FlushProperties);

    if (!(ppl = ValidPropHandle(hProps)))
        return FALSE;

    return FlushPIFData(ppl, (flOpt & FLUSHPROPS_DISCARD));
}


/** EnumProperties - enumerate open properties
 *
 * INPUT
 *  hProps = handle to previous properties (NULL to start)
 *
 * OUTPUT
 *  next property handle, 0 if none
 */

HANDLE WINAPI EnumProperties(HANDLE hProps)
{
    PPROPLINK ppl;
    FunctionName(EnumProperties);

    if (!hProps)
        return g_pplHead;

    if (!(ppl = ValidPropHandle(hProps)))
        return NULL;

    return ppl->pplNext;
}


/** AssociateProperties - associate data with property info
 *
 * INPUT
 *  hProps = handle to properties
 *  iAssociate = association index (eg, HVM_ASSOCIATION)
 *  lData = new associated data
 *
 * OUTPUT
 *  previously associated data for the index, 0 if none (or error)
 *
 * NOTES
 *  If iAssociate is a negative association index, then the current
 *  associated value is returned and not modified (ie, lData is ignored)
 */

LONG_PTR WINAPI AssociateProperties(HANDLE hProps, int iAssociate, LONG_PTR lData)
{
    LONG_PTR l;
    int iIndex;
    PPROPLINK ppl;
    FunctionName(AssociateProperties);

    if (!(ppl = ValidPropHandle(hProps)))
        return FALSE;

    iIndex = iAssociate;
    if (iIndex < 0)
        iIndex *= -1;

    switch(iIndex) {
    case HVM_ASSOCIATION:
        l = ppl->hVM;
        break;
    case HWND_ASSOCIATION:
        l = (LONG_PTR)ppl->hwndTty;
        break;
    case LPARGS_ASSOCIATION:
        l = (LONG_PTR)ppl->lpArgs;
        break;
    default:
        return FALSE;
    }

    switch(iAssociate) {
    case HVM_ASSOCIATION:
        ppl->hVM = (DWORD) lData;
        break;
    case HWND_ASSOCIATION:
        ppl->hwndTty = (HWND)lData;
        break;
    case LPARGS_ASSOCIATION:
        ppl->lpArgs = (LPTSTR)lData;
        break;
    }
    return l;
}


void SetBootDrive(LPTSTR pszFile)
{
    TCHAR szPath[10];
    DWORD cbPath = sizeof(szPath);

    szPath[0] = 0;
    SHGetValue(HKEY_LOCAL_MACHINE, TEXT("Software\\Microsoft\\Windows\\CurrentVersion\\Setup"), TEXT("BootDir"), NULL, szPath, &cbPath);

    pszFile[0] = szPath[0] ? szPath[0] : TEXT('C');
}

#define RENAME_DELETENEW    0x0001
#define RENAME_CREATEOLD    0x0002
#define RENAME_DELETEOLD    0x0004

/** RenameStartupFiles - rename startup files (for real-mode only)
 *
 * INPUT
 *  pszOld -> old filename (eg, C:CONFIG.SYS)
 *  pszNew -> new filename (eg, C:CONFIG.WOS)
 *  flRename == see RENAME_* above
 *
 * OUTPUT
 *  None
 */

void RenameStartupFiles(LPTSTR pszOld, LPTSTR pszNew, int flRename)
{
    SetBootDrive(pszOld);
    SetBootDrive(pszNew);

    // For RENAME_DELETENEW:  make sure there's no way an existing new filename
    // could block the rename process, not even a R/H/S file!

    if (flRename & RENAME_DELETENEW) {
        SetFileAttributes(pszNew, 0);
        DeleteFile(pszNew);
    }

    // For RENAME_CREATEOLD:  if the old filename doesn't exist, then we
    // want to create a hidden 0-length copy of the new filename, as a way
    // of telling ourselves later that the old file didn't exist.  Then,
    // when it's later (see RENAME_DELETEOLD), if the file we're to rename
    // is a hidden 0-length file, just delete it.

    if (flRename & RENAME_CREATEOLD) {

        int i;
        HANDLE hFile;

        i = (int)GetFileAttributes(pszOld);
        if (i == -1) {
            hFile = CreateFile( pszNew,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                CREATE_ALWAYS,
                                FILE_ATTRIBUTE_HIDDEN,
                                NULL
                               );
            if (hFile != INVALID_HANDLE_VALUE)
                CloseHandle(hFile);
            return;
        }
    }

    // For RENAME_DELETEOLD:  if the old file is a hidden 0-length
    // dummy file that we presumably created ourselves, then simply delete it.

    if (flRename & RENAME_DELETEOLD) {

        WIN32_FIND_DATA dFind;
        HANDLE hFind = FindFirstFile(pszOld, &dFind);
        if (hFind != INVALID_HFINDFILE) {
            FindClose(hFind);
            if ((dFind.dwFileAttributes & FILE_ATTRIBUTE_HIDDEN) && dFind.nFileSizeLow == 0) {
                DeleteFile(pszOld);
                return;
            }
        }
    }
    MoveFile(pszOld, pszNew);
}


/** CheckForceReboot - return TRUE if exit to real-mode has been disabled
 *
 *  This function largely copied from win\core\user\inexit.c -JTP
 *
 *  The key being examined is:
 *
 *      HKEY_LOCAL_MACHINE\\System\\CurrentControlSet\\Control\\Shutdown
 *
 *  The value-name is:
 *
 *      ForceReboot
 *
 *  The value to force a reboot is:
 *
 *      "1"
 *
 * INPUT
 *  None
 *
 * OUTPUT
 *  TRUE if reboot must be forced, FALSE if not
 */

BOOL CheckForceReboot()
{
    // Check to see if we have to force a reboot of the system. One reason is
    // so that the double space utilities can tell us the system must be reboot
    // since we can't exit to real mode double space...

    HKEY     hkey;
    static TCHAR szShutdown[] = REGSTR_PATH_SHUTDOWN;
    BOOL     fForce=FALSE;
    TCHAR    sz[12];
    DWORD    dwType;

    if (!RegOpenKey(HKEY_LOCAL_MACHINE, szShutdown, &hkey))
    {

        LONG cb = sizeof(sz);
        static TCHAR szForceReboot[] = REGSTR_VAL_FORCEREBOOT;

        if (!SHQueryValueEx(hkey, szForceReboot, NULL, &dwType, (LPBYTE)sz, &cb))
        {
            if (*sz==TEXT('1'))
                fForce = TRUE;
        }

        RegCloseKey(hkey);
    }

    return(fForce);
}


/** CreateStartupProperties - create startup files (for real-mode only)
 *
 * INPUT
 *  hProps = handle to properties
 *  flOpt = CREATEPROPS_NONE (all bits reserved)
 *
 * OUTPUT
 *  TRUE (1) if any files were created, FALSE (0) if there was an error
 *  creating 1 or more of the necessary files, and -1 there was no error and
 *  no files needed to be created.
 */

int WINAPI CreateStartupProperties(HANDLE hProps, UINT flOpt)
{
    BOOL fForceReboot;
    int fSuccess = FALSE;
    PPROPLINK ppl;
    FunctionName(CreateStartupProperties);

    if (NULL != (ppl = ValidPropHandle(hProps))) {

        InitRealModeFlag(ppl);

        if (ppl->flProp & PROP_REALMODE) {

            // WriteAdvPrgData only returns the exact value TRUE (1) if it
            // actually created a file.  If there was no need to create
            // a particular file, and therefore no error, it returns -1.

            fForceReboot = CheckForceReboot();
            fSuccess = WriteAdvPrgData(ppl, MAX_CONFIG_SIZE, szCONFIGHDRSIG40, g_szMConfigFile, g_szConfigFile, fForceReboot, fForceReboot);
            fSuccess &= WriteAdvPrgData(ppl, MAX_AUTOEXEC_SIZE, szAUTOEXECHDRSIG40, g_szMAutoexecFile, g_szAutoexecFile, fSuccess, fForceReboot);

        }
    }
    return fSuccess;
}


/** DoLargeEnvironmentSubstA - do large environment substitution
 *
 *  NOTE: the below comment is no longer true.  DoEnvironmentSubst
 *        calls ExpandEnvironmentStrings which does not have this restriction!
 *
 *  This is a wrapper around shell's DoEnvironmentSubst that can handle
 *  larger buffers.  Although not well-documented, DoEnvironmentSubst will
 *  fail if the input buffer simply contains more than 256 chars, hence
 *  it is not entirely suitable for our purposes.
 *
 *  The basic idea is:  find CR/LF-terminated lines, copy them to szTmp
 *  with a null terminator instead of the CR/LF, do environment substitution
 *  on szTmp, move the data in psz up by the number of bytes szTmp grew (if
 *  any), and then copy the expanded string back into psz.  Repeat until no
 *  more lines.
 */

int DoLargeEnvironmentSubstA(LPSTR psz, int cchsz, BOOL fStripWIN)
{
    int cch, cchNew, cchDiff;
    DWORD dwResult;
    CHAR szTmp[256];
    CHAR *pszCRLF, *pszCRLFNext;

    pszCRLF = psz;
    while (NULL != (pszCRLFNext = (PBYTE)(UINT_PTR)(ULONG_PTR)_fstrstr(pszCRLF, szCRLF))) {

        cch = (int)(pszCRLFNext - pszCRLF);
        pszCRLFNext += ARRAYSIZE(szCRLF)-1;

        if (cch >= ARRAYSIZE(szTmp))
            goto Next;

        hmemcpy(szTmp, pszCRLF, cch);
        szTmp[cch] = '\0';

        // fStripWIN means: strip WIN wherever you find it and do NOTHING else

        if (fStripWIN) {
            if (lstrcmpiA(szTmp, szWin) == 0) {
                cchNew = 0;
                goto Strip;
            }
            goto Next;
        }

        cchNew = cch;
        dwResult = DoEnvironmentSubstA(szTmp, ARRAYSIZE(szTmp));
        if (HIWORD(dwResult))
            cchNew = LOWORD(dwResult);

    Strip:
        cchDiff = cchNew - cch;            // cchDiff == # chars needed
        if (cchDiff) {

            if (cchDiff > cchsz - (lstrlenA(psz)+1))
                goto Next;              // not enough room left

            cch = lstrlenA(pszCRLFNext)+1;// cch == # chars required to copy

            hmemcpy(pszCRLFNext+(cchDiff*sizeof(CHAR)), pszCRLFNext, cch*sizeof(CHAR));
            pszCRLFNext += cchDiff;
        }

        hmemcpy(pszCRLF, szTmp, cchNew*sizeof(CHAR));
        hmemcpy(pszCRLF+cchNew, szCRLF, (ARRAYSIZE(szCRLF)-1)*sizeof(CHAR));

      Next:
        pszCRLF = pszCRLFNext;
    }
    return lstrlenA(psz);
}

void ReadAdvPrgData(PPROPLINK ppl, int cbMax, LPCSTR lpszName, LPTSTR pszFile, UINT flOpt)
{
    HANDLE hFile;
    int i;
    DWORD cbData = 0, cb;
    CHAR  szTmp[256];
    PBYTE pb, pbLast;
    PBYTE pbData = NULL, pbDataTmp, pbDataOrig = NULL;

    SetBootDrive(pszFile);

    if (flOpt & DELETEPROPS_DISCARD) {
        DeleteFile(pszFile);
        return;
    }

    hFile = CreateFile( pszFile,
                        GENERIC_READ,
                        FILE_SHARE_READ,
                        NULL,
                        OPEN_EXISTING,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL );

    if (hFile != INVALID_HANDLE_VALUE) {

        pbData = (PBYTE)LocalAlloc(LPTR, cbMax);

        if (pbData) {

            pbDataOrig = pbData;

            if (ReadFile( hFile, (PVOID)pbData, (DWORD)cbMax-1, &cbData, NULL))
            {

                // Guess what, this routine isn't so general purpose after all:
                // in the autoexec case, we must find the signature that precedes
                // the command-line that we added to the file.  For expediency,
                // we simply delete it and everything after it.

                if (pszFile == g_szMConfigFile) {

                    // If "DOS=SINGLE" is present, skip over it

                    if (pbData == (PBYTE)(UINT_PTR)(ULONG_PTR)_fstrstr(pbData, szSingle)) {
                        cb = lstrlenA(szSingle);
                        pbData += cb;
                        cbData -= cb;
                    }
                }

                if (pszFile == g_szMAutoexecFile) {

                    // If "@echo off" is present, skip over it

                    if (LoadStringA(g_hinst, IDS_AUTOEXECTOP, szTmp, ARRAYSIZE(szTmp))) {

                        if (pbData == (PBYTE)(UINT_PTR)(ULONG_PTR)_fstrstr(pbData, szTmp)) {
                            cb = lstrlenA(szTmp);
                            pbData += cb;
                            cbData -= cb;
                        }
                    }

                    // Try to find our signature at the end of the file now

                    if (LoadStringA(g_hinst, IDS_AUTOEXECBOTTOM, szTmp, ARRAYSIZE(szTmp))) {

                        pb = pbData;
                        pbLast = NULL;
                        while (NULL != (pb = (PBYTE)(UINT_PTR)(ULONG_PTR)_fstrstr(pb, szTmp))) {
                            pbLast = pb++;
                        }

                        // If there was a (last) occurrence of the signature
                        // in the file, then it's starting point is where we cut
                        // off the data we're about to rewrite.

                        if (pbLast)
                            cbData = (DWORD) (pbLast - pbData);
                    }
                }
            }
        }
        CloseHandle(hFile);

        DeleteFile(pszFile);
    }

    // Before we simply blast the data back out to the PIF, we check to
    // see if the data we're about to write is the same as what's already
    // there after environment string substitution.  If so, we leave the
    // data alone, so that environment strings are left in their pre-expanded
    // form whenever possible (and with a minimum of work on our part).

    i = -1;
    if (NULL != (pbDataTmp = (PBYTE)LocalAlloc(LPTR, cbMax))) {
        if (PifMgr_GetProperties(ppl, lpszName, pbDataTmp, cbMax-1, GETPROPS_NONE)) {
            DoLargeEnvironmentSubstA(pbDataTmp, cbMax, FALSE);
            i = lstrcmpA(pbDataTmp, pbData);
        }
        EVAL(LocalFree(pbDataTmp) == NULL);
    }

    // Note that if the file no longer exists, or does but is zero bytes
    // long, this call effectively removes the data from the PIF.  Otherwise,
    // the PIF gets refreshed with the contents of the startup file, which
    // we deleted immediately after pulling its contents out.

    if (i != 0)
        PifMgr_SetProperties(ppl, lpszName, pbData, cbData, SETPROPS_NONE);

    if (pbDataOrig)
        EVAL(LocalFree(pbDataOrig) == NULL);
}


BOOL WriteAdvPrgData(PPROPLINK ppl, int cbMax, LPCSTR lpszName, LPTSTR pszFile, LPTSTR pszOrigFile, BOOL fCreateAnyway, BOOL fForceReboot)
{
    HANDLE hFile;
    int cbData, cb, i, j;
    PBYTE pbData;
    PROPPRG prg;
    PROPENV env;
    DWORD   dwBytes;
    BOOL fPause = FALSE;
    BOOL fSuccess = FALSE;
    CHAR szTmp[MAXPATHNAME];

    cbData = PifMgr_GetProperties(ppl, lpszName, NULL, 0, GETPROPS_NONE);

    if (!cbData) {              // if there's no autoexec data
        if (fCreateAnyway != 1) // and no previous file was created
            return -1;          // then there's no need to create this file
    }

    // If fCreateAnyway is -1, then we know are being called for AUTOEXEC
    // *and* that no file was created for CONFIG.  Since we are committed to
    // creating an AUTOEXEC now, we therefore *force* the CONFIG file to
    // be created as well.  That way, the user won't end up with the default
    // CONFIG (he may have just not have needed/cared about device drivers, but
    // still wanted to run in real-mode).

    if (fCreateAnyway == -1) {
        if (!WriteAdvPrgData(ppl, MAX_CONFIG_SIZE, szCONFIGHDRSIG40, g_szMConfigFile, g_szConfigFile, TRUE, fForceReboot))
            return FALSE;
    }

    pbData = (PBYTE)LocalAlloc(LPTR, cbMax);
    if (pbData) {
        if (cbData) {
            if (cbData >= cbMax)
                cbData = cbMax-1;
            PifMgr_GetProperties(ppl, lpszName, pbData, cbData, GETPROPS_NONE);

        } else if (fForceReboot) {

            SetBootDrive(pszOrigFile);

            hFile = CreateFile( pszOrigFile,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ,
                                NULL,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL
                               );

            if (hFile != INVALID_HANDLE_VALUE) {

                cbData = (int)SetFilePointer( hFile, 0, NULL, FILE_END );
                if (cbData) {
                    if (cbData >= cbMax)
                        cbData = cbMax-1;
                    SetFilePointer( hFile, 0, NULL, FILE_BEGIN );
                    if (ReadFile( hFile, pbData, cbData, &dwBytes, NULL)) {
                        if (dwBytes != (UINT)cbData) {
                            cbData = 0;
                        } else {
                            cbData = (int)dwBytes;
                        }

                    }
                }

                CloseHandle(hFile);
            }
        }
    }
    else
        cbData = 0;

    SetBootDrive( pszFile );

    hFile = CreateFile( pszFile,
                        GENERIC_READ | GENERIC_WRITE,
                        FILE_SHARE_READ,
                        NULL,
                        CREATE_ALWAYS,
                        FILE_ATTRIBUTE_NORMAL,
                        NULL );

    if (hFile!=INVALID_HANDLE_VALUE) {

        // Do environment string substitution on the buffer before writing
        // it out.  Treat it like the huge null-terminated string that it is.

        if (pbData)
            cbData = DoLargeEnvironmentSubstA(pbData, cbMax, fForceReboot);

        // Do the simple thing:  ALWAYS write out the "@echo off" line;
        // if someone really wants to see the batch file execute, let them
        // put an explicit "echo on" in their autoexec edit control;  since it
        // comes after ours, it will override as appropriate.
        //
        // This rule also simplifies conversion on the way back:  if the
        // first line is "@echo off", we delete it.

        if (pszFile == g_szMConfigFile) {
            WriteFile( hFile, szSingle, lstrlenA(szSingle), &dwBytes, NULL );
        }

        if (pszFile == g_szMAutoexecFile) {
            szTmp[0] = '\0';
            LoadStringA(g_hinst, IDS_AUTOEXECTOP, szTmp, ARRAYSIZE(szTmp));
            WriteFile( hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL );
        }

        if (WriteFile(hFile, pbData, cbData, &dwBytes, NULL) && (dwBytes==(DWORD)cbData))
            fSuccess++;         // return TRUE to indicate file created

        // Make sure the user-defined gob-o-goo is CR/LF-terminated;
        // if not, spit one out.

        if (cbData != 0)
            if (cbData < 2 || lstrcmpA(pbData+cbData-(ARRAYSIZE(szCRLF)-1), szCRLF) != 0)
                WriteFile( hFile, szCRLF, sizeof(szCRLF)-1, &dwBytes, NULL);

        // Guess what, this routine isn't so general purpose after all:
        // in the autoexec case, we must append the command-line to
        // the file, followed by C:\WINDOWS\WIN.COM /W to restore everything

        if (pszFile == g_szMAutoexecFile) {

            if (PifMgr_GetProperties(ppl, MAKELP(0,GROUP_PRG),
                                &prg, sizeof(prg), GETPROPS_NONE)) {

                szTmp[0] = '\0';
                LoadStringA(g_hinst, IDS_AUTOEXECBOTTOM, szTmp, ARRAYSIZE(szTmp));
                WriteFile(hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL);

                // If the program is on drive A, then it is time emit:
                //
                //  ECHO Please insert the diskette for Mickey's ABCs in drive A now
                //  PAUSE
                //
                // since WinOldAp will have asked the user to remove it.

                if ((ppl->szPathName[0] == TEXT('a') ||
                     ppl->szPathName[0] == TEXT('A')) && ppl->szPathName[1] == TEXT(':')) {

                    CHAR szTmpFmt[128];

                    fPause++;
                    WriteFile(hFile, szEcho, sizeof(szEcho)-1, &dwBytes, NULL);

                    szTmpFmt[0] = '\0';
                    LoadStringA(g_hinst, IDS_DISKINSERT, szTmpFmt, ARRAYSIZE(szTmpFmt));

                    // The DISKINSERT string should have one %s for the title...

                    wsprintfA(szTmp, szTmpFmt, prg.achTitle);

                    WriteFile(hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL);
                    WriteFile(hFile, szPause, sizeof(szPause)-1, &dwBytes, NULL);
                }

                if (PifMgr_GetProperties(ppl, MAKELP(0,GROUP_ENV),
                                    &env, sizeof(env), GETPROPS_NONE)) {

                    if (env.achBatchFile[0]) {

                        WriteFile(hFile, szCall, sizeof(szCall)-1, &dwBytes, NULL);

                        // env.achBatchFile could be a long filename and/or
                        // quoted (since it could be followed by args).  Take
                        // care of it now.

                        i = lstrunquotefnameA(szTmp, env.achBatchFile, ARRAYSIZE(szTmp), TRUE);

                        j = lstrskipfnameA(env.achBatchFile);

                        if (env.achBatchFile[j])
                            lstrcpynA(szTmp+i, env.achBatchFile+j, ARRAYSIZE(szTmp)-i);

                        WriteFile(hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL);
                        WriteFile(hFile, szCRLF, sizeof(szCRLF)-1, &dwBytes, NULL);
                    }
                }
#ifdef DBCS
//                else if (!GetProperties(ppl, lpszName, NULL, 0, GETPROPS_NONE))
//                    _lwrite(hFile, szDOSIME, lstrlen(szDOSIME));
#endif

                if (!prg.achWorkDir[0]) {

                    // If there's no hard-coded working directory,
                    // create one.  It's ok to party on prg.achWorkDir because
                    // we're not going to call PifMgr_SetProperties and inadvertently
                    // change it.

                    InitWorkDir(ppl, &prg, NULL);
                }

                if (prg.achWorkDir[0]) {

                    cb = lstrunquotefnameA(szTmp, prg.achWorkDir, ARRAYSIZE(szTmp), TRUE);

                    if (szTmp[1] == ':') {
                        WriteFile(hFile, szTmp, 2, &dwBytes, NULL);
                        WriteFile(hFile, szCRLF, ARRAYSIZE(szCRLF)-1, &dwBytes, NULL);
                    }
                    WriteFile(hFile, szCD, sizeof(szCD)-1, &dwBytes, NULL);
                    WriteFile(hFile, szTmp, cb, &dwBytes, NULL);
                    WriteFile(hFile, szCRLF, sizeof(szCRLF)-1, &dwBytes, NULL);
                }

                // We now always CALL the cmd-line, in case it's a batch file.
                // If it isn't, no biggie, because command.com is smart enough
                // to ignore it.

                WriteFile(hFile, szCall, sizeof(szCall)-1, &dwBytes, NULL);

                // If the properties we have are ones *we* created in the
                // PIF dir, and it looks like the application name passed to
                // PifMgr_OpenProperties is a "qualified" pathname, then we'll use
                // that instead of the path stored in the PIF, since the app
                // might have moved since *we* created the PIF.

                if ((ppl->flProp & PROP_PIFDIR) &&
                    (ppl->szPathName[0] == TEXT('\\') ||
                     ppl->szPathName[0] && ppl->szPathName[1] == TEXT(':'))) {

                    // We're making this call not because ppl->szPathName
                    // is quoted (it shouldn't be), but because it could still
                    // be a long filename, so translate it.

                    CHAR achPathName[ ARRAYSIZE(ppl->szPathName) ];
                    PifMgr_WCtoMBPath( ppl->szPathName, achPathName, ARRAYSIZE(achPathName) );
                    i = lstrunquotefnameA(szTmp, achPathName, ARRAYSIZE(szTmp), -1);
                }
                else {

                    // As for the thing stored in the PIF, now *that* could
                    // both long and quoted.  Take care of it now.

                    i = lstrunquotefnameA(szTmp, prg.achCmdLine, ARRAYSIZE(szTmp), TRUE);
                }

                // NOTE: There is an obscure case where the preceding call
                // to lstrunquotefname can fail, returning i == 0, even though
                // WinOldAp already verified the app exists.  That case is when
                // the app name is NOT fully-qualified, so it exists somewhere
                // on the PATH, but NOT in the current directory, and the app
                // name is an LFN.  Because lstrunquotefname must call INT 21h,
                // function 7160h, subfunction 1 (NAMTRN_DO83QUERY), IFSMGR must
                // find the equivalent 8.3 name for the LFN app name, but all
                // IFSMGR has to work with is the current directory, and that's
                // not where the app is, so the INT 21h fails, returning error
                // #3 (ERROR_PATH_NOT_FOUND).
                //
                // To solve, we could do another PATH search for the app file,
                // via OpenFileEx;  if found, it would return the fully-qualifed
                // long-file-name of the app, in the OEM char set, which we
                // could then perform NameTrans on, to get full 8.3.  Yuck! -JP


                // If there are any arguments in the PIF, find them
                // and append them, giving PREFERENCE to any args that WINOLDAP
                // specifically associated with this instance of the app.

                if (ppl->lpArgs) {      // any associated args?
                    j = lstrlen(ppl->lpArgs);
                    if (j) {
                        szTmp[i++] = ' ';
                        if (ppl->lpArgs[j-1] == TEXT('\r'))
                            j--;
                        {
                            CHAR achArgs[ ARRAYSIZE(ppl->lpArgs) ];

                            WideCharToMultiByte( CP_ACP, 0,
                                                 ppl->lpArgs, -1,
                                                 achArgs, ARRAYSIZE(achArgs),
                                                 NULL, NULL );
                            lstrcpynA(szTmp+i, achArgs, min(sizeof(szTmp)-i,(unsigned)j+1));
                        }
                    }
                } else {
                    j = lstrskipfnameA(prg.achCmdLine);

                    if (prg.achCmdLine[j])
                        lstrcpynA(szTmp+i, prg.achCmdLine+j, ARRAYSIZE(szTmp)-i);
                }

                WriteFile(hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL);
                WriteFile(hFile, szCRLF, sizeof(szCRLF)-1, &dwBytes, NULL);

                // If we paused above to ask for a disk, we should pause
                // again to ask them to remove the disk.  We won't actually
                // emit a PAUSE though unless close-on-exit is set, since
                // WIN.COM should already pause for them when the cursor isn't
                // at 0,0.

                if (fPause) {
                    WriteFile(hFile, szEcho, sizeof(szEcho)-1, &dwBytes, NULL);

                    szTmp[0] = TEXT('\0');
                    LoadStringA(g_hinst, IDS_DISKREMOVE, szTmp, ARRAYSIZE(szTmp));

                    WriteFile(hFile, szTmp, lstrlenA(szTmp), &dwBytes, NULL);

                    if (prg.flPrg & PRG_CLOSEONEXIT)
                        WriteFile(hFile, szPause, sizeof(szPause)-1, &dwBytes, NULL);
                    else
                        WriteFile(hFile, szCRLF, sizeof(szCRLF)-1, &dwBytes, NULL);
                }
            }
        }
        CloseHandle(hFile);
    }
    if (pbData)
        EVAL(LocalFree(pbData) == NULL);

    return fSuccess;
}


/** PifMgr_CloseProperties - close property info for application
 *
 * INPUT
 *  hProps = handle to properties
 *  flOpt = CLOSEPROPS_DISCARD to abandon cached PIF data, otherwise save it
 *
 * OUTPUT
 *  NULL if successful, otherwise hProps is returned as given
 */

HANDLE WINAPI PifMgr_CloseProperties(HANDLE hProps, UINT flOpt)
{
    PPROPLINK ppl;
    FunctionName(PifMgr_CloseProperties);

    if (!(ppl = ValidPropHandle(hProps)))
        return hProps;

    // When discarding on a close, set the SKIPPIF flag, so that the
    // flush code won't say "oh, not only should I throw away my current
    // set of data, but I should read in clean data" -- new data is no use
    // since the caller is closing.

    if (flOpt & CLOSEPROPS_DISCARD)
        ppl->flProp |= PROP_SKIPPIF;

    if (ppl->flProp & PROP_DIRTY) {     // this redundant check added
                                        // to avoid making FlushPIFData PRELOAD -JTP

        // Note that we avoid calling FlushPIFData if INHIBITPIF is set,
        // since FlushPIFData will just return a fake TRUE result anyway.
        // But we don't want to be fooled, we want to make sure the block
        // gets unlocked now.

        if ((ppl->flProp & PROP_INHIBITPIF) || !FlushPIFData(ppl, (flOpt & CLOSEPROPS_DISCARD))) {

            // If FlushPIFData failed, then if we still have an outstanding
            // dirty lock, force the data to become unlocked, by clearing the
            // dirty flag in the middle of a pair otherwise pointless lock/unlock
            // calls (because that's the nice, clean way to do it!)

            if (ppl->flProp & PROP_DIRTYLOCK) {
                ppl->cLocks++;
                ppl->flProp &= ~PROP_DIRTY;
                ppl->cLocks--;
            }
        }
    }

    if (ppl->lpPIFData) {
        LocalFree(ppl->lpPIFData);
        ppl->lpPIFData = NULL;
    }

    if (ppl->hPIF != INVALID_HANDLE_VALUE)
        CloseHandle(ppl->hPIF);

    // Unlink from the global list

    if (ppl->pplPrev)
        ppl->pplPrev->pplNext = ppl->pplNext;
    else
        g_pplHead = ppl->pplNext;

    if (ppl->pplNext)
        ppl->pplNext->pplPrev = ppl->pplPrev;

    LocalFree(ppl);
    return NULL;
}


/** ValidPropHandle - verify handle
 *
 * INPUT
 *  hProps = handle to properties
 *
 * OUTPUT
 *  pointer to prop, NULL otherwise
 */

PPROPLINK ValidPropHandle(HANDLE hProps)
{
    FunctionName(ValidPropHandle);
    if (!hProps ||
        (HANDLE)hProps > g_offHighestPropLink ||
        ((PPROPLINK)hProps)->iSig != PROP_SIG) {
        ASSERTFAIL();
        return NULL;
    }
    return (PPROPLINK)hProps;
}


/** ResizePIFData - verify handle and resize PIF data
 *
 * INPUT
 *  ppl -> property
 *  cbResize = bytes to resize PIF data by
 *
 * OUTPUT
 *  previous size of PIF data if successful, -1 if not
 *
 *  on success, the PIF data is returned LOCKED, so successful
 *  ResizePIFData calls should be matched with UnlockPIFData calls.
 */

int ResizePIFData(PPROPLINK ppl, INT cbResize)
{
    INT cbOld, cbNew;
    void *lpNew;
    BOOL fInitStdHdr = FALSE;
    FunctionName(ResizePIFData);

    ASSERTTRUE(cbResize != 0);

    // Cope with empty or old PIF files

    cbOld = ppl->cbPIFData;
    cbNew = ppl->cbPIFData + cbResize;

    if ((cbNew < cbOld) == (cbResize > 0))
        return -1;      // underflow/overflow

    if (!ppl->lpPIFData && cbOld == 0) {
        if (cbNew >= sizeof(STDPIF) + sizeof(PIFEXTHDR))
            fInitStdHdr = TRUE;
        lpNew = LocalAlloc(LPTR, cbNew);
    }
    else
    {

        if (cbOld == sizeof(STDPIF))
        {
            fInitStdHdr = TRUE;
            cbOld += sizeof(PIFEXTHDR);
            cbNew += sizeof(PIFEXTHDR);
        }

        lpNew = LocalReAlloc( ppl->lpPIFData, cbNew, LMEM_MOVEABLE|LMEM_ZEROINIT);

    }

    if (lpNew) {
        ppl->cbPIFData = cbNew;
        ppl->lpPIFData = (LPPIFDATA)lpNew;
        ppl->cLocks++;
        if (fInitStdHdr) {
            lstrcpyA(ppl->lpPIFData->stdpifext.extsig, szSTDHDRSIG);
            ppl->lpPIFData->stdpifext.extnxthdrfloff = LASTHDRPTR;
            ppl->lpPIFData->stdpifext.extfileoffset = 0x0000;
            ppl->lpPIFData->stdpifext.extsizebytes = sizeof(STDPIF);
        }
        return cbOld;
    }
    return -1;
}



/** GetPIFData - read PIF data back from PIF
 *
 * INPUT
 *  ppl -> property
 *  fLocked == TRUE to return data locked, FALSE unlocked
 *
 * OUTPUT
 *  TRUE if succeeded, FALSE if not
 */

BOOL GetPIFData(PPROPLINK ppl, BOOL fLocked)
{
    DWORD dwOff;
    LPTSTR pszOpen;
    BOOL fSuccess = FALSE;
    FunctionName(GetPIFData);

    // Since we're going to (re)load the property data now, reset
    // the current size, so that ResizePIFData will resize it from zero

    ppl->cbPIFData = 0;

    // If SKIPPIF is set (eg, by PifMgr_OpenProperties), then don't
    // try to open anything (since PifMgr_OpenProperties already tried!),

    if (ppl->hPIF == INVALID_HANDLE_VALUE && !(ppl->flProp & PROP_SKIPPIF)) {
        pszOpen = g_szDefaultPIF;
        if (!(ppl->flProp & PROP_DEFAULTPIF))
            pszOpen = ppl->ofPIF.szPathName;
        ppl->hPIF = CreateFile( pszOpen,
                                GENERIC_READ | GENERIC_WRITE,
                                FILE_SHARE_READ | FILE_SHARE_WRITE,
                                NULL,
                                OPEN_EXISTING,
                                FILE_ATTRIBUTE_NORMAL,
                                NULL );
    }
    if (ppl->hPIF == INVALID_HANDLE_VALUE) {

        // The following warning is disabled because the presence of
        // the dialog box got WINOLDAP stuck in an infinite message loop -JTP

        InitProperties(ppl, fLocked);
        goto Exit;
    }
    dwOff = SetFilePointer(ppl->hPIF, 0, NULL, FILE_END);
    if (dwOff >= sizeof(STDPIF)) {

        ppl->flProp |= PROP_REGEN;

        if (ResizePIFData(ppl, dwOff) != -1) {

            SetFilePointer(ppl->hPIF, 0, NULL, FILE_BEGIN);
          
            if (ReadFile( ppl->hPIF, ppl->lpPIFData,
                         ppl->cbPIFData, &ppl->cbPIFData, NULL ))
            {

                // Can't be dirty anymore, 'cause we just read the PIF back in

                ppl->flProp &= ~PROP_DIRTY;

                if (ppl->flProp & PROP_DEFAULTPIF) {

                    WideCharToMultiByte( CP_ACP, 0,
                                         ppl->szPathName+ppl->iFileName,
                                         -1,
                                         ppl->lpPIFData->stdpifdata.appname,
                                         ARRAYSIZE(ppl->lpPIFData->stdpifdata.appname),
                                         NULL, NULL
                                        );

                    PifMgr_WCtoMBPath( ppl->szPathName,
                                       ppl->lpPIFData->stdpifdata.startfile,
                                       ARRAYSIZE(ppl->lpPIFData->stdpifdata.startfile)
                                      );
                    // I don't think this is generally worth dirtying the
                    // property info for, because otherwise every app that used
                    // _DEFAULT.PIF initially would get its own PIF file created
                    // later;  PIF file creation should only take place when
                    // substantive changes have been made

                    // ppl->flProp |= PROP_DIRTY;
                }

                // If we're not dealing with an enhanced PIF, then we
                // go to the various INI files to retrieve DOS app defaults

                if (!GetGroupData(ppl, szWENHHDRSIG40, NULL, NULL)) {
                    GetINIData();
                }

                // If we're not dealing with a new NT/UNICODE PIF, then
                // we add a new section so it's ALWAYS there when we're
                // UNICODE enabled.

                if (!GetGroupData(ppl, szWNTHDRSIG40, NULL, NULL)) {
                    VERIFYTRUE(AddGroupData(ppl, szWNTHDRSIG40, NULL, sizeof(WNTPIF40)));
                }
                // If we're not dealing with a NT PIF, then
                // we add the NT sections so it's ALWAYS there when we're
                // running on NT.

                if (!GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL)) {
                    LPWNTPIF31 lpnt31;

                    VERIFYTRUE(AddGroupData(ppl, szWNTHDRSIG31, NULL, sizeof(WNTPIF31)));
                    if (NULL != (lpnt31 = GetGroupData(ppl, szWNTHDRSIG31, NULL, NULL))) {
                        lstrcpyA( lpnt31->nt31Prop.achConfigFile, NT_CONFIG_FILE );
                        lstrcpyA( lpnt31->nt31Prop.achAutoexecFile, NT_AUTOEXEC_FILE );
                    }
                }

                if (!fLocked)
                    ppl->cLocks--;  // UnlockPIFData(ppl);
                fSuccess++;
            }
        }
        else
            ASSERTFAIL();

        ppl->flProp &= ~PROP_REGEN;
    }
    CloseHandle(ppl->hPIF);
    ppl->hPIF = INVALID_HANDLE_VALUE;

    // As long as IGNOREPIF isn't set, clear SKIPPIF, because even if we
    // already knew the PIF didn't exist on *this* call, one may be created
    // (by someone else) by the next time we're called

  Exit:
    if (!(ppl->flProp & PROP_IGNOREPIF))
        ppl->flProp &= ~PROP_SKIPPIF;
    return fSuccess;
}


/** FlushPIFData - write dirty PIF data back to PIF
 *
 * INPUT
 *  ppl -> property
 *  fDiscard == TRUE to discard dirty data, FALSE to keep it
 *
 * OUTPUT
 *  TRUE if succeeded, FALSE if not
 *
 * NOTES
 *  We must first check the PROPLINK and see if the DONTWRITE bit has
 *  been set, in which case we have to fail the flush.  Once DONTWRITE is
 *  set in a PROPLINK, it will never be cleared, unless the caller
 *  specifies fDiscard == TRUE to reload the data.  This is BY DESIGN (ie,
 *  a UI compromise).  How does DONTWRITE get set?  By someone else
 *  having previously (and successfully) done a flush to the same PIF; at
 *  that point in time, we will look for all other properties that refer to
 *  the same file, and set their DONTWRITE bit.  What about PROPLINKs that
 *  are created later?  They're ok, they don't get DONTWRITE set until
 *  the above sequence takes place during their lifetime.
 */

BOOL FlushPIFData(PPROPLINK ppl, BOOL fDiscard)
{
    UINT u;
    BOOL fSuccess = FALSE;
    FunctionName(FlushPIFData);

    // If nothing dirty, nothing to do

    if (!(ppl->flProp & PROP_DIRTY) || (ppl->flProp & PROP_INHIBITPIF))
        return TRUE;            // ie, success

    // If discarding, then clear PROP_DIRTY and reload the data

    if (fDiscard) {
        ppl->flProp &= ~(PROP_DIRTY | PROP_DONTWRITE);
        return GetPIFData(ppl, FALSE);
    }

    if (ppl->flProp & PROP_DONTWRITE)
        return fSuccess;        // ie, FALSE (error)

    if (!ppl->lpPIFData)
        return fSuccess;        // ie, FALSE (error)

    ppl->cLocks++;

    // If we created properties without opening a file, it may have
    // been because normal PIF search processing was overridden by the
    // presence of a WIN.INI entry;  if that entry is still there,
    // then our data is not in sync with any existing file, nor is there
    // any point in creating a new file as long as that entry exists.  We
    // need to consider prompting the user as to whether he really wants
    // that WIN.INI entry, so that it's clear what the heck is going on

    if (ppl->flProp & PROP_IGNOREPIF) {

        HANDLE hProps;

        ppl->ckbMem = GetProfileInt(apszAppType[APPTYPE_PIF]+1, ppl->szPathName+ppl->iFileName, -1);
        if (ppl->ckbMem != -1)
            goto Exit;

        // The WIN.INI entry apparently went away, so let's re-attempt to
        // open the properties that we should have obtained in the first
        // place.  Assuming success, we will copy our entire block on top of
        // them (thereby flushing it), and also copy their PIF name to our
        // PIF name and their PIF flags to our PIF flags, so that future
        // flushes are of the more normal variety

        hProps = PifMgr_OpenProperties(ppl->ofPIF.szPathName, NULL, 0, OPENPROPS_RAWIO);
        if (hProps) {
            ppl->flProp &= ~(PROP_IGNOREPIF | PROP_SKIPPIF);
            ppl->flProp |= ((PPROPLINK)hProps)->flProp & (PROP_IGNOREPIF | PROP_SKIPPIF);
            lstrcpy(ppl->ofPIF.szPathName, ((PPROPLINK)hProps)->ofPIF.szPathName);
            if (PifMgr_SetProperties(hProps, NULL, ppl->lpPIFData, ppl->cbPIFData, SETPROPS_RAWIO) == ppl->cbPIFData) {
                fSuccess++;
                ppl->flProp &= ~(PROP_DIRTY | PROP_TRUNCATE);
            }
            PifMgr_CloseProperties(hProps, CLOSEPROPS_NONE);
        }
        goto Exit;
    }

    // Disable annoying critical error popups (NO MORE GOTOS PAST HERE PLEASE)

    u = SetErrorMode(SEM_FAILCRITICALERRORS);

    ppl->hPIF = CreateFile( ppl->ofPIF.szPathName,
                            GENERIC_READ | GENERIC_WRITE,
                            FILE_SHARE_READ | FILE_SHARE_WRITE,
                            NULL,
                            OPEN_ALWAYS,
                            FILE_ATTRIBUTE_NORMAL,
                            NULL );

    // If we couldn't open the file, then the presumption is that the
    // app didn't have a PIF (or did but someone but someone deleted it),
    // and so we use the name we constructed during PifMgr_OpenProperties in case
    // they ever opted to save new settings (which they obviously have done!)

    // 28-Feb-95: If the PIF did exist at one time (meaning NOCREATPIF is
    // set), then don't recreate it;  somebody's trying to delete their own
    // PIF, so let them. -JTP

    if ((ppl->hPIF != INVALID_HANDLE_VALUE) && (GetLastError()!=ERROR_FILE_EXISTS)) {

        if (!(ppl->flProp & PROP_NOCREATEPIF))
            SetFilePointer( ppl->hPIF, 0, NULL, FILE_BEGIN );

        // If the create succeeded, we're no longer using the default PIF

        if (ppl->hPIF != INVALID_HANDLE_VALUE) {

            ppl->flProp |= PROP_NOCREATEPIF;

            ppl->flProp &= ~(PROP_TRUNCATE | PROP_NOPIF | PROP_DEFAULTPIF);
        }
    }

    // If either the open or the create succeeded, write the PIF data out now

    if (ppl->hPIF != INVALID_HANDLE_VALUE) {

        PPROPLINK pplEnum;
        DWORD dwDummy;

        WriteFile( ppl->hPIF, (LPCVOID)ppl->lpPIFData,
                   ppl->cbPIFData, &dwDummy, NULL );
        if (ppl->flProp & PROP_TRUNCATE)
            WriteFile(ppl->hPIF, (LPCVOID)ppl->lpPIFData, 0, &dwDummy, NULL );
        CloseHandle(ppl->hPIF);
        ppl->hPIF = INVALID_HANDLE_VALUE;
        ppl->flProp &= ~(PROP_DIRTY | PROP_TRUNCATE);
        fSuccess++;

        // Here's where we want to check for other active PROPLINKs using the
        // same PIF.  For each one found, set its DONTWRITE bit.

        pplEnum = NULL;
        while (NULL != (pplEnum = (PPROPLINK)EnumProperties(pplEnum))) {
            if (lstrcmpi(ppl->ofPIF.szPathName, pplEnum->ofPIF.szPathName) == 0) {
                if (pplEnum != ppl)
                    pplEnum->flProp |= PROP_DONTWRITE;
            }
        }
    }

    // Re-enable annoying critical error popups

    SetErrorMode(u);

  Exit:
    ppl->cLocks--;
    return fSuccess;
}


/** AddEnhancedData - create enhanced section(s) of PIF data
 *
 * INPUT
 *  ppl -> property
 *
 * OUTPUT
 *  lpenh or NULL
 */

LPWENHPIF40 AddEnhancedData(PPROPLINK ppl, LPW386PIF30 lp386)
{
    PROPPRG prg;
    PROPTSK tsk;
    PROPVID vid;
    PROPKBD kbd;
    PROPMSE mse;
    PROPFNT fnt;
    PROPWIN win;
    PROPENV env;
    void *aDataPtrs[NUM_DATA_PTRS];
    LPWENHPIF40 lpenh = NULL;

    FunctionName(AddEnhancedData);

    // Get copies of pre-enhanced and/or default settings first,
    // and do them all *before* doing the AddGroupData, because the
    // functions' behavior will change once the enhanced section is added;

    // in addition, zero those strucs that contain strings, since lstrcpy()
    // may initialize a minimum of 1 byte, leaving garbage in the rest.

    BZero(&prg, sizeof(prg));
    BZero(&fnt, sizeof(fnt));
    BZero(&win, sizeof(win));
    BZero(&env, sizeof(env));
    BZero(aDataPtrs, sizeof(aDataPtrs));

    aDataPtrs[ LP386_INDEX ] = (LPVOID)lp386;
    GetPrgData(ppl, aDataPtrs, &prg, sizeof(prg), GETPROPS_NONE);
    GetTskData(ppl, aDataPtrs, &tsk, sizeof(tsk), GETPROPS_NONE);
    GetVidData(ppl, aDataPtrs, &vid, sizeof(vid), GETPROPS_NONE);
    GetKbdData(ppl, aDataPtrs, &kbd, sizeof(kbd), GETPROPS_NONE);
    GetMseData(ppl, aDataPtrs, &mse, sizeof(mse), GETPROPS_NONE);
    GetFntData(ppl, aDataPtrs, &fnt, sizeof(fnt), GETPROPS_NONE);
    GetWinData(ppl, aDataPtrs, &win, sizeof(win), GETPROPS_NONE);
    GetEnvData(ppl, aDataPtrs, &env, sizeof(env), GETPROPS_NONE);


    if (AddGroupData(ppl, szWENHHDRSIG40, NULL, sizeof(WENHPIF40))) {

        if (NULL != (lpenh = GetGroupData(ppl, szWENHHDRSIG40, NULL, NULL))) {

            lpenh->dwEnhModeFlagsProp = prg.dwEnhModeFlags;
            lpenh->dwRealModeFlagsProp = prg.dwRealModeFlags;
            lstrcpyA(lpenh->achOtherFileProp, prg.achOtherFile);
            lstrcpyA(lpenh->achIconFileProp, prg.achIconFile);
            lpenh->wIconIndexProp = prg.wIconIndex;
            lpenh->tskProp = tsk;
            lpenh->vidProp = vid;
            lpenh->kbdProp = kbd;
            lpenh->mseProp = mse;
            lpenh->fntProp = fnt;
            lpenh->winProp = win;
            lpenh->envProp = env;
            lpenh->wInternalRevision = WENHPIF40_VERSION;
        }
    }
    return lpenh;
}


/** AddGroupData - add NEW property group to PIF data
 *
 * INPUT
 *  ppl -> property
 *  lpszGroup -> name of new group
 *  lpGroup -> new group record (if NULL, then group data is zero-filled)
 *  cbGroup == size of new group record
 *
 * OUTPUT
 *  TRUE if successful, FALSE if not
 */

BOOL AddGroupData(PPROPLINK ppl, LPCSTR lpszGroup, LPCVOID lpGroup, int cbGroup)
{
    INT cbOld;
    LPPIFEXTHDR lpph;
    FunctionName(AddGroupData);

    if ((cbOld = ResizePIFData(ppl, cbGroup+sizeof(PIFEXTHDR))) != -1) {

        lpph = (LPPIFEXTHDR)LPPIF_FIELDOFF(stdpifext);

        while ((DWORD_PTR)lpph <= (DWORD_PTR)LPPIF_OFF(cbOld - sizeof(PIFEXTHDR)) &&
               (DWORD_PTR)lpph >= (DWORD_PTR)LPPIF_FIELDOFF(stdpifext)) {
            if (lpph->extnxthdrfloff == LASTHDRPTR) {
                lpph->extnxthdrfloff = (WORD) cbOld;
                lpph = (LPPIFEXTHDR)LPPIF_OFF(cbOld);
                lstrcpynA(lpph->extsig, lpszGroup, sizeof(lpph->extsig));
                lpph->extnxthdrfloff = LASTHDRPTR;
                lpph->extfileoffset = (INT)(cbOld + sizeof(PIFEXTHDR));
                lpph->extsizebytes = (WORD) cbGroup;
                if (lpGroup) {
                    hmemcpy((LPBYTE)LPPH_OFF(sizeof(PIFEXTHDR)), lpGroup, cbGroup);
                    ppl->flProp |= PROP_DIRTY;
                }
                break;
            }
            lpph = (LPPIFEXTHDR)LPPIF_OFF(lpph->extnxthdrfloff);
        }
        ppl->cLocks--;
        return TRUE;
    }
    ASSERTFAIL();
    return FALSE;
}


/** RemoveGroupData - remove EXISTING property group from PIF data
 *
 * INPUT
 *  ppl -> property
 *  lpszGroup -> name of group
 *
 * OUTPUT
 *  TRUE if successful, FALSE if not
 */

BOOL RemoveGroupData(PPROPLINK ppl, LPCSTR lpszGroup)
{
    INT cbGroup, fSuccess;
    LPBYTE lpGroup;
    WORD extnxthdrfloff;
    LPPIFEXTHDR lpph, lpphGroup;
    FunctionName(RemoveGroupData);

    ppl->cLocks++;

    fSuccess = FALSE;
    if (NULL != (lpGroup = GetGroupData(ppl, lpszGroup, &cbGroup, &lpphGroup))) {

        // Removing groups is a bit tedious, so here goes....

        // First, we will walk all the headers, attempting to find the
        // one that points to the one we're about to remove, and point it
        // to the next one, and at the same time adjust all file offsets that
        // equal or exceed the offsets of either the outgoing data or its
        // header.

        lpph = (LPPIFEXTHDR)LPPIF_FIELDOFF(stdpifext);

        while ((DWORD_PTR)lpph <= (DWORD_PTR)LPPIF_OFF(ppl->cbPIFData - sizeof(PIFEXTHDR)) &&
               (DWORD_PTR)lpph >= (DWORD_PTR)LPPIF_FIELDOFF(stdpifext)) {

            extnxthdrfloff = lpph->extnxthdrfloff;

            if ((DWORD_PTR)LPPH_OFF(lpph->extfileoffset) >= (DWORD_PTR)lpGroup)
                lpph->extfileoffset -= (WORD) cbGroup;

            if (lpphGroup) {
                if ((DWORD_PTR)LPPH_OFF(lpph->extfileoffset) >= (DWORD_PTR)lpphGroup)
                    lpph->extfileoffset -= sizeof(PIFEXTHDR);
                if ((DWORD_PTR)LPPH_OFF(lpph->extnxthdrfloff) == (DWORD_PTR)lpphGroup)
                    extnxthdrfloff = lpph->extnxthdrfloff = lpphGroup->extnxthdrfloff;
            }
            if (extnxthdrfloff == LASTHDRPTR)
                break;

            if ((DWORD_PTR)LPPH_OFF(lpph->extnxthdrfloff) >= (DWORD_PTR)lpGroup)
                lpph->extnxthdrfloff -= (WORD) cbGroup;

            if (lpphGroup)
                if ((DWORD_PTR)LPPH_OFF(lpph->extnxthdrfloff) >= (DWORD_PTR)lpphGroup)
                    lpph->extnxthdrfloff -= sizeof(PIFEXTHDR);

            lpph = (LPPIFEXTHDR)LPPIF_OFF(extnxthdrfloff);
        }

        // Next, move everything up over the data, then adjust lpph as
        // needed and move everything up over the header (this must be done
        // in two discrete steps, because we shouldn't assume anything
        // about the data's location relative to its header).

        hmemcpy(lpGroup, (LPBYTE)lpGroup+cbGroup,
                (DWORD_PTR)LPPIF_OFF(ppl->cbPIFData) - (DWORD_PTR)((LPBYTE)lpGroup+cbGroup));

        if (lpphGroup) {

            if ((DWORD_PTR)lpphGroup >= (DWORD_PTR)((LPBYTE)lpGroup+cbGroup))
                lpphGroup -= cbGroup;

            hmemcpy(lpphGroup, lpphGroup+1,
                    (DWORD_PTR)LPPIF_OFF(ppl->cbPIFData) - (DWORD_PTR)((LPBYTE)lpphGroup+1+cbGroup));

            cbGroup += sizeof(PIFEXTHDR);
        }
        ResizePIFData(ppl, -cbGroup);
        ppl->flProp |= PROP_DIRTY | PROP_TRUNCATE;
        ppl->cLocks--;
    }
    ppl->cLocks--;
    return fSuccess;
}


/** GetGroupData - get ptr to property group (by name)
 *
 * INPUT
 *  ppl -> property (assumes it is LOCKED)
 *  lpszGroup -> property group; may be one of the following:
 *      "WINDOWS 286 3.0"
 *      "WINDOWS 386 3.0"
 *      "WINDOWS PIF.400"
 *    or any other group name that is the name of a valid PIF extension.
 *    if NULL, then *lpcbGroup is a 0-based index of the group we are looking for
 *  lpcbGroup -> where to return size of group data (NULL if not)
 *  lplpph -> where to return ptr to pif extension header, if any (NULL if not)
 *
 * OUTPUT
 *  Returns ptr to property group info, NULL if not found
 */

void *GetGroupData(PPROPLINK ppl, LPCSTR lpszGroup,
                    LPINT lpcbGroup, LPPIFEXTHDR *lplpph)
{
    BOOL fFixMe;
    LPPIFEXTHDR lpph;
    FunctionName(GetGroupData);

    if (!ppl->lpPIFData)
        return NULL;

    lpph = (LPPIFEXTHDR)LPPIF_FIELDOFF(stdpifext);

    while ((DWORD_PTR)lpph <= (DWORD_PTR)LPPIF_OFF(ppl->cbPIFData-sizeof(PIFEXTHDR)) &&
           (DWORD_PTR)lpph >= (DWORD_PTR)LPPIF_FIELDOFF(stdpifext)) {

        // PIFEDIT 3.x can trash the first byte of our extended portion
        // (generally with a zero), so try to recover by stuffing the first
        // character of the group we're looking for into the signature;
        // if the rest of the signature matches, great, if it doesn't, then
        // re-zero it.

        if (!lpszGroup) {
            // searching by index *lpcbGroup
            if (!(*lpcbGroup)--) {
                if (lplpph)
                    *lplpph = lpph;
                *lpcbGroup = lpph->extsizebytes;
                return lpph;
            }
        }
        else {
            if (FALSE != (fFixMe = !lpph->extsig[0]))      // attempt to fix
                lpph->extsig[0] = *lpszGroup;
            if (lstrcmpiA(lpph->extsig, lpszGroup) == 0) {
                if (lplpph)
                    *lplpph = lpph;
                if (lpcbGroup)
                    *lpcbGroup = lpph->extsizebytes;
                if (lpph->extfileoffset >= (WORD)ppl->cbPIFData) {
                    ASSERTFAIL();
                    return NULL;
                }
                return (LPBYTE)LPPIF_OFF(lpph->extfileoffset);
            }
            if (fFixMe)                         // fix failed (this time anyway)
                lpph->extsig[0] = 0;
        }
        if (lpph->extnxthdrfloff == LASTHDRPTR)
            break;
        lpph = (LPPIFEXTHDR)LPPIF_OFF(lpph->extnxthdrfloff);
    }

    // If we didn't get anywhere, check if this is a "really old" PIF;
    // ie, one without any headers;  if so, then if all they were asking for
    // was the old stuff, return it

    if (ppl->cbPIFData == sizeof(STDPIF) && lpszGroup) {
        if (lstrcmpiA(szSTDHDRSIG, lpszGroup) == 0) {
            if (lplpph)
                *lplpph = NULL;
            if (lpcbGroup)
                *lpcbGroup = sizeof(STDPIF);
            return ppl->lpPIFData;
        }
    }
    return NULL;
}

/** AppWizard - call the AppWizard CPL (appwiz.cpl)
 */



TCHAR c_szAPPWIZ[]    = TEXT("appwiz.cpl");
CHAR  c_szAppWizard[] = "AppWizard";

typedef DWORD (WINAPI *LPAPPWIZARD)(HWND hwnd, HANDLE i, UINT ui);

UINT WINAPI AppWizard(HWND hwnd, HANDLE hProps, UINT action)
{
    DWORD err = 42;
    LPAPPWIZARD XAppWizard;
    HINSTANCE hAppWizard;

    hAppWizard = LoadLibrary(c_szAPPWIZ);

    if (hAppWizard)
    {
        if (NULL != (XAppWizard = (LPAPPWIZARD)GetProcAddress(hAppWizard, c_szAppWizard)))
        {
            err = XAppWizard( hwnd, hProps, action );
        }
        FreeLibrary((HINSTANCE)hAppWizard);
    }

    return (UINT)err;
}
