/***************************************************************************
* Quick fix application tools
*
* Author: clupu (Feb 16, 2000)
*
* History:
*
* rparsons  -   11/10/2000    -    Modified titles on common dialogs
*
* rparsons  -   11/23/2000    -    Added ability to save XML to file
*
* rparsons  -   11/25/2000    -    Modified to allow matching file on a
*                                  different drive to be selected
*
* rparsons  -   05/19/2001    -    Added context menu on file tree.
*                                  Added URL for WU package.
*                                  Added Remove Matching button.
*                                  Converted shim list to list view.
*
* rparsons  -   07/06/2001    -    Converted static tab control to use
*                                  child dialogs.
*
\**************************************************************************/

#include "afxwin.h"
#include "commctrl.h"
#include "commdlg.h"
#include "shlwapi.h"
#include "shellapi.h"
#include "shlobj.h"
#include "shlobjp.h"    // needed for Link Window support
#include "uxtheme.h"    // needed for tab control theme support
#include "resource.h"
#include <tchar.h>
#include <aclapi.h>

#include "QFixApp.h"
#include "dbSupport.h"

extern "C" {
#include "shimdb.h"
}

CWinApp theApp;

/*
 * Global Variables
 */

HINSTANCE g_hInstance;
HWND      g_hDlg;
HWND      g_hLayersDlg;
HWND      g_hFixesDlg;

HWND      g_hwndTab;
HWND      g_hwndListLayers;

TCHAR     g_szAppTitle[64];

TCHAR     g_szBinary[MAX_PATH];         // the full path of the main binary being shimmed
TCHAR     g_szShortName[128];           // the short name of the main EXE

TCHAR     g_szParentExeName[MAX_PATH];  // the short name of the parent EXE
TCHAR     g_szParentExeFullPath[MAX_PATH]; // the full path of the parent EXE

TCHAR     g_szSDBToDelete[MAX_PATH];    // the SDB file to delete from a previous 'Run'

int       g_nCrtTab;

HWND      g_hwndShimList;               // the handle to the list view control
                                        // containing all the shims available

HWND      g_hwndFilesTree;              // the handle to the tree view control
                                        // containing the matching files selected

HWND      g_hwndModuleList;             // the handle to the list view control
                                        // containing module information

BOOL      g_bSimpleEdition;             // simple or dev edition

BOOL      g_fW2K;                       // Win2K or XP

RECT      g_rcDlgBig, g_rcDlgSmall;     // rectangle of the simple and the dev edition
                                        // of the dialog

BOOL      g_bAllShims;

BOOL      g_bSelectedParentExe;         // flag to indicate if a parent EXE has been
                                        // selected

PFIX      g_pFixHead;

TCHAR     g_szXPUrl[] = _T("hcp://services/subsite?node=TopLevelBucket_4/")
                        _T("Fixing_a_problem&topic=MS-ITS%3A%25HELP_LOCATION")
                        _T("%25%5Cmisc.chm%3A%3A/compatibility_tab_and_wizard.htm")
                        _T("&select=TopLevelBucket_4/Fixing_a_problem/")
                        _T("Application_and_software_problems");

TCHAR     g_szW2KUrl[] = _T("http://www.microsoft.com/windows2000/")
                         _T("downloads/tools/appcompat/");


#define ID_COUNT_SHIMS  1234

typedef HRESULT (*PFNEnableThemeDialogTexture)(HWND hwnd, DWORD dwFlags);

#if DBG

void
LogMsgDbg(
    LPTSTR pszFmt,
    ... 
    )
/*++
    LogMsgDbg

    Description:    DbgPrint.

--*/
{
    TCHAR gszT[1024];
    va_list arglist;

    va_start(arglist, pszFmt);
    _vsntprintf(gszT, 1023, pszFmt, arglist);
    gszT[1023] = 0;
    va_end(arglist);

    OutputDebugString(gszT);
}

#endif // DBG

BOOL
IsUserAnAdministrator(
    void
    )
/*++
    IsUserAnAdministrator

    Description:    Determine if the currently logged on user is an admin.

--*/
{
    HANDLE                      hToken;
    DWORD                       dwStatus = 0, dwAccessMask = 0;
    DWORD                       dwAccessDesired = 0, dwACLSize = 0;
    DWORD                       dwStructureSize = sizeof(PRIVILEGE_SET);
    PACL                        pACL = NULL;
    PSID                        psidAdmin = NULL;
    BOOL                        fReturn = FALSE;
    PRIVILEGE_SET               ps;
    GENERIC_MAPPING             GenericMapping;
    PSECURITY_DESCRIPTOR        psdAdmin           = NULL;
    SID_IDENTIFIER_AUTHORITY    SystemSidAuthority = SECURITY_NT_AUTHORITY;

    // AccessCheck() requires an impersonation token
    if (!ImpersonateSelf(SecurityImpersonation)) {
        goto cleanup;
    }

    // Attempt to access the token for the current thread
    if (!OpenThreadToken(GetCurrentThread(),
                         TOKEN_QUERY,
                         FALSE,
                         &hToken)) {
        
        if (GetLastError() != ERROR_NO_TOKEN) {
             goto cleanup;
        }
    
        // If the thread does not have an access token, we'll 
        // examine the access token associated with the process.
        if (!OpenProcessToken(GetCurrentProcess(),
                              TOKEN_QUERY,
                              &hToken)) {
            goto cleanup;
        }
        
    }

    // Build a SID for administrators group
    if (!AllocateAndInitializeSid(&SystemSidAuthority,
                                  2,
                                  SECURITY_BUILTIN_DOMAIN_RID,
                                  DOMAIN_ALIAS_RID_ADMINS,
                                  0, 0, 0, 0, 0, 0,
                                  &psidAdmin)) {
        goto cleanup;
    }
    
    // Allocate memory for the security descriptor
    psdAdmin = HeapAlloc(GetProcessHeap(),
                         HEAP_ZERO_MEMORY,
                         SECURITY_DESCRIPTOR_MIN_LENGTH);

    if (psdAdmin == NULL) {
         goto cleanup;
    }
    
    // Initialize the new security descriptor
    if (!InitializeSecurityDescriptor(psdAdmin,
                                      SECURITY_DESCRIPTOR_REVISION)) {
         goto cleanup;
    }
    
    // Compute size needed for the ACL
    dwACLSize = sizeof(ACL) + sizeof(ACCESS_ALLOWED_ACE) +
                GetLengthSid(psidAdmin) - sizeof(DWORD);

    // Allocate memory for ACL
    pACL = (PACL) HeapAlloc(GetProcessHeap(),
                            HEAP_ZERO_MEMORY,
                            dwACLSize);

    if (pACL == NULL) {
         goto cleanup;
    }
    
    // Initialize the new ACL
    if (!InitializeAcl(pACL,
                       dwACLSize,
                       ACL_REVISION2)) {
         goto cleanup;
    }
    
    dwAccessMask = ACCESS_READ | ACCESS_WRITE;

    // Add the access-allowed ACE to the DACL
    if (!AddAccessAllowedAce(pACL,
                             ACL_REVISION2,
                             dwAccessMask,
                             psidAdmin)) {
         goto cleanup;
    }
    
    // Set our DACL to the security descriptor
    if (!SetSecurityDescriptorDacl(psdAdmin,
                                   TRUE,
                                   pACL,
                                   FALSE)) {
         goto cleanup;
    }
    
    // AccessCheck is sensitive about the format of the
    // security descriptor; set the group & owner
    SetSecurityDescriptorGroup(psdAdmin, psidAdmin, FALSE);
    SetSecurityDescriptorOwner(psdAdmin, psidAdmin, FALSE);

    // Ensure that the SD is valid
    if (!IsValidSecurityDescriptor(psdAdmin)) {
         goto cleanup;
    }
    
    dwAccessDesired = ACCESS_READ;

    // Initialize GenericMapping structure even though we
    // won't be using generic rights.
    GenericMapping.GenericRead    = ACCESS_READ;
    GenericMapping.GenericWrite   = ACCESS_WRITE;
    GenericMapping.GenericExecute = 0;
    GenericMapping.GenericAll     = ACCESS_READ | ACCESS_WRITE;

    // After all that work, it boils down to this call
    if (!AccessCheck(psdAdmin,
                     hToken,
                     dwAccessDesired,
                     &GenericMapping,
                     &ps,           
                     &dwStructureSize,
                     &dwStatus,
                     &fReturn)) {
        goto cleanup;
    }
    
    RevertToSelf();

cleanup:

    if (pACL) {
        HeapFree(GetProcessHeap(), 0, pACL);
    }
    
    if (psdAdmin){
        HeapFree(GetProcessHeap(), 0, psdAdmin);
    }
        
    if (psidAdmin){
        FreeSid(psidAdmin);
    }
    
    return (fReturn);
}

BOOL
CheckForSDB(
    void
    )
/*++
    CheckForSDB

    Description:    Attempts to locate sysmain.sdb in the apppatch directory.

--*/
{
    HANDLE  hFile;
    TCHAR   szSDBPath[MAX_PATH];
    BOOL    fResult = FALSE;

    if (!GetSystemWindowsDirectory(szSDBPath, MAX_PATH)) {
        return FALSE;
    }

    _tcscat(szSDBPath, _T("\\apppatch\\sysmain.sdb"));

    hFile = CreateFile(szSDBPath,
                       GENERIC_READ,
                       FILE_SHARE_READ | FILE_SHARE_WRITE,
                       NULL,
                       OPEN_EXISTING,
                       FILE_ATTRIBUTE_NORMAL,
                       NULL);

    if (INVALID_HANDLE_VALUE != hFile) {
        CloseHandle(hFile);
        fResult = TRUE;
    }

    return (fResult);
}

void
AddModuleToListView(
    TCHAR*  pModuleName,
    UINT    uOption
    )
/*++
    AddModuleToListView

    Description:    Adds the specified module to the list view.

--*/
{
    LVITEM  lvi;
    int     nIndex;
    TCHAR   szInclude[MAX_PATH];
    TCHAR   szExclude[MAX_PATH];

    LoadString(g_hInstance, IDS_INCLUDE_HDR, szInclude, MAX_PATH);
    LoadString(g_hInstance, IDS_EXCLUDE_HDR, szExclude, MAX_PATH);

    lvi.mask     = LVIF_TEXT | LVIF_PARAM;
    lvi.lParam   = uOption == BST_CHECKED ? 1 : 0;
    lvi.pszText  = uOption == BST_CHECKED ? szInclude : szExclude;
    lvi.iItem    = ListView_GetItemCount(g_hwndModuleList);
    lvi.iSubItem = 0;

    nIndex = ListView_InsertItem(g_hwndModuleList, &lvi);

    ListView_SetItemText(g_hwndModuleList,
                         nIndex,
                         1,
                         pModuleName);
}

void
BuildModuleListForShim(
    PFIX  pFix,
    DWORD dwFlags
    )
/*++
    BuildModuleListForShim

    Description:    Based on the flag, adds modules to the list view for
                    the specified shim or retrieves them and adds them
                    to the linked list.
                    
--*/
{
    PMODULE pModule, pModuleTmp, pModuleNew;
    int     nItemCount = 0, nIndex;
    LVITEM  lvi;
    TCHAR   szBuffer[MAX_PATH];

    if (dwFlags & BML_ADDTOLISTVIEW) {
        
        //
        // Walk the linked list and add the modules to the list view.
        //
        pModule = pFix->pModule;

        while (pModule) {
            
            AddModuleToListView(pModule->pszName,
                                pModule->fInclude ? BST_CHECKED : 0);

            pModule = pModule->pNext;
        }
        
    }
     
    if (dwFlags & BML_DELFRLISTVIEW) {

        pModule = pFix->pModule;
        
        while (NULL != pModule) {

            pModuleTmp = pModule->pNext;

            HeapFree(GetProcessHeap(), 0, pModule->pszName);
            HeapFree(GetProcessHeap(), 0, pModule);

            pModule = pModuleTmp;
        }

        pFix->pModule = NULL;

    }

    if (dwFlags & BML_GETFRLISTVIEW) {
    
        pModule = pFix->pModule;
        
        while (NULL != pModule) {

            pModuleTmp = pModule->pNext;

            HeapFree(GetProcessHeap(), 0, pModule->pszName);
            HeapFree(GetProcessHeap(), 0, pModule);

            pModule = pModuleTmp;
        }

        pFix->pModule = NULL;

        //
        // Get each shim from the list view and add it to the list.
        //
        nItemCount = ListView_GetItemCount(g_hwndModuleList);

        if (nItemCount == 0) {
            return;
        }

        for (nIndex = nItemCount - 1; nIndex >= 0; nIndex--) {
        
            lvi.mask     = LVIF_PARAM;
            lvi.iItem    = nIndex;
            lvi.iSubItem = 0;

            ListView_GetItem(g_hwndModuleList, &lvi);

            ListView_GetItemText(g_hwndModuleList, nIndex, 1, szBuffer, MAX_PATH);
    
            pModuleNew = (PMODULE)HeapAlloc(GetProcessHeap(),
                                            HEAP_ZERO_MEMORY,
                                            sizeof(MODULE));
    
            pModuleNew->pszName = (TCHAR*)HeapAlloc(GetProcessHeap(),
                                                    HEAP_ZERO_MEMORY,
                                                    sizeof(TCHAR) * (lstrlen(szBuffer) + 1));
    
            if (pModuleNew == NULL || pModuleNew->pszName == NULL) {
                LogMsg(_T("[BuildModuleListForShim] Couldn't allocate memory to store module info."));
                return;
            }
            
            lstrcpy(pModuleNew->pszName, szBuffer);
            pModuleNew->fInclude = (BOOL)lvi.lParam;

            pModuleNew->pNext = pFix->pModule;
            pFix->pModule = pModuleNew;
        }
    }
}

int
CountShims(
    BOOL fCountSelected
    )
/*++
    CountShims

    Description:    Counts the number of selected shims in the list and
                    updates the text on the dialog.
--*/
{
    int     cShims = 0, nTotalShims, nShims = 0;
    BOOL    fReturn;
    TCHAR   szShims[MAX_PATH];
    TCHAR   szTemp[MAX_PATH];

    cShims = ListView_GetItemCount(g_hwndShimList);

    if (fCountSelected) {
        
        for (nTotalShims = 0; nTotalShims < cShims; nTotalShims++) {
    
            fReturn = ListView_GetCheckState(g_hwndShimList, nTotalShims);
    
            if (fReturn) {
                nShims++;
            }
        }
    }

    LoadString(g_hInstance, IDS_SEL_CAPTION, szTemp, MAX_PATH);
    wsprintf(szShims, szTemp, nShims, cShims);

    SetDlgItemText(g_hFixesDlg, IDC_SELECTED_SHIMS, szShims);

    return (cShims);
}

void
DisplayAttrContextMenu(
    POINT* pt
    )
/*++
    DisplayAttrContextMenu

    Description:    Displays a popup menu for the attributes tree.
    
--*/

{
    HMENU hPopupMenu, hTrackPopup;
                                              
    //
    // Load the popup menu and display it.
    //
    hPopupMenu = LoadMenu(g_hInstance, MAKEINTRESOURCE(IDM_ATTR_POPUP));

    if (hPopupMenu == NULL) {
        return;
    }

    hTrackPopup = GetSubMenu(hPopupMenu, 0);

    TrackPopupMenu(hTrackPopup,
                   TPM_LEFTBUTTON | TPM_NOANIMATION | TPM_LEFTALIGN,
                   pt->x, pt->y, 0, g_hDlg, NULL);

    DestroyMenu(hPopupMenu);
}

void
InsertListViewColumn(
    HWND   hWndListView,
    LPTSTR lpColumnName,
    BOOL   fCenter,
    int    nColumnID,
    int    nSize
    )
/*++
    InsertListViewColumn

    Description:    Wrapper for ListView_InsertColumn.
    
--*/
{
    LV_COLUMN   lvColumn;

    if (fCenter) {
        lvColumn.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM | LVCF_FMT;
    } else {
        lvColumn.mask = LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
    }

    //
    // Fill in the structure and add the column.
    //
    lvColumn.fmt        =   LVCFMT_CENTER;
    lvColumn.cx         =   nSize;
    lvColumn.iSubItem   =   0;
    lvColumn.pszText    =   lpColumnName; 
    ListView_InsertColumn(hWndListView, nColumnID, &lvColumn);
}


void
EnableTabBackground(
    HWND hDlg
    )
{
    PFNEnableThemeDialogTexture pFnEnableThemeDialogTexture;
    HMODULE                     hUxTheme;
    
    hUxTheme = (HMODULE)LoadLibrary(_T("uxtheme.dll"));
    if (hUxTheme) {
        pFnEnableThemeDialogTexture = (PFNEnableThemeDialogTexture)
                                            GetProcAddress(hUxTheme, "EnableThemeDialogTexture");
        if (pFnEnableThemeDialogTexture) {
            pFnEnableThemeDialogTexture(hDlg, ETDT_USETABTEXTURE);
        }
        
        FreeLibrary(hUxTheme);
    }
}


void
HandleLayersDialogInit(
    HWND hDlg
    )
{
    HWND    hParent;
    DLGHDR* pHdr;

    g_hLayersDlg = hDlg;
    
    hParent = GetParent(hDlg);

    pHdr = (DLGHDR*)GetWindowLongPtr(hParent, DWLP_USER);

    //
    // Position the dialog within the tab.
    //
    SetWindowPos(hDlg, HWND_TOP, 
                 pHdr->rcDisplay.left, pHdr->rcDisplay.top,
                 pHdr->rcDisplay.right - pHdr->rcDisplay.left,
                 pHdr->rcDisplay.bottom - pHdr->rcDisplay.top,
                 0);

    g_hwndListLayers = GetDlgItem(hDlg, IDC_LAYERS);

    EnableTabBackground(hDlg);
}

BOOL
HandleFixesDialogInit(
    HWND hDlg
    )
{
    HWND    hParent;
    DLGHDR* pHdr;
    int     nCount = 0;
    TCHAR   szColumn[MAX_PATH];

    g_hFixesDlg = hDlg;
    
    hParent = GetParent(hDlg);

    pHdr = (DLGHDR*)GetWindowLongPtr(hParent, DWLP_USER);

    //
    // Position the dialog within the tab.
    //
    SetWindowPos(hDlg, HWND_TOP, 
                 pHdr->rcDisplay.left, pHdr->rcDisplay.top,
                 pHdr->rcDisplay.right - pHdr->rcDisplay.left,
                 pHdr->rcDisplay.bottom - pHdr->rcDisplay.top,
                 0);

    g_hwndShimList = GetDlgItem(hDlg, IDC_SHIMS);

    //
    // Set up the shim list.
    //
    LoadString(g_hInstance, IDS_FIXNAME_COLUMN, szColumn, MAX_PATH);
    InsertListViewColumn(g_hwndShimList, szColumn, FALSE, 0, 200);
    LoadString(g_hInstance, IDS_CMDLINE_COLUMN, szColumn, MAX_PATH);
    InsertListViewColumn(g_hwndShimList, szColumn, TRUE, 1, 59);
    LoadString(g_hInstance, IDS_MODULE_COLUMN, szColumn, MAX_PATH);
    InsertListViewColumn(g_hwndShimList, szColumn, TRUE, 2, 52);

    ListView_SetExtendedListViewStyle(g_hwndShimList,
                                      LVS_EX_CHECKBOXES | LVS_EX_FULLROWSELECT);

    //
    // Query the database and show the available general purpose fixes.
    //
    ShowAvailableFixes(g_hwndShimList);

    nCount = CountShims(FALSE);

    if (!nCount) {
        return FALSE;
    }

    ListView_SetItemCount(g_hwndShimList, nCount);

    EnableTabBackground(hDlg);

    return TRUE;
}

DLGTEMPLATE*
LockDlgRes(
    LPCTSTR lpResName
    ) 
{ 
    HRSRC hrsrc = FindResource(NULL, lpResName, RT_DIALOG); 

    if (NULL == hrsrc) {
        return NULL;
    }
    
    HGLOBAL hglb = LoadResource(g_hInstance, hrsrc);

    if (NULL == hglb) {
        return NULL;
    }
    
    return (DLGTEMPLATE*)LockResource(hglb); 
}

void
InitTabs(
    HWND hMainDlg,
    HWND hTab
    )
{
    DLGHDR* pHdr;
    TCITEM  tcitem;
    RECT    rcTab;
    int     nCount;
    TCHAR   szTabText[MAX_PATH];
    TCHAR   szError[MAX_PATH];
    
    pHdr = (DLGHDR*)HeapAlloc(GetProcessHeap(),
                              HEAP_ZERO_MEMORY,
                              sizeof(DLGHDR));

    if (NULL == pHdr) {
        LoadString(g_hInstance, IDS_TAB_SETUP_FAIL, szError, MAX_PATH);
        MessageBox(hMainDlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
        return;
    }

    //
    // Save away a pointer to the structure.
    //
    SetWindowLongPtr(hMainDlg, DWLP_USER, (LONG_PTR)pHdr);
    
    //
    // Save away the handle to the tab control.
    //
    pHdr->hTab = hTab;

    //
    // Add the tabs.
    //
    LoadString(g_hInstance, IDS_TAB_FIRST_TEXT, szTabText, MAX_PATH);
    tcitem.mask     = TCIF_TEXT | TCIF_PARAM;
    tcitem.pszText  = szTabText;
    tcitem.lParam   = 0;
    TabCtrl_InsertItem(pHdr->hTab, 0, &tcitem);

    LoadString(g_hInstance, IDS_TAB_SECOND_TEXT, szTabText, MAX_PATH);
    tcitem.pszText = szTabText;
    tcitem.lParam  = 1;
    TabCtrl_InsertItem(pHdr->hTab, 1, &tcitem);

    //
    // Lock the resources for two child dialog boxes.
    //
    pHdr->pRes[0] = LockDlgRes(MAKEINTRESOURCE(IDD_LAYERS_TAB));
    pHdr->pDlgProc[0] = LayersTabDlgProc;
    pHdr->pRes[1] = LockDlgRes(MAKEINTRESOURCE(IDD_FIXES_TAB));
    pHdr->pDlgProc[1] = FixesTabDlgProc;

    //
    // Determine the bounding rectangle for all child dialog boxes.
    //
    GetWindowRect(pHdr->hTab, &rcTab);
    TabCtrl_AdjustRect(pHdr->hTab, FALSE, &rcTab);
    InflateRect(&rcTab, 1, 1);
    rcTab.left -= 2;
    
    MapWindowPoints(NULL, hMainDlg, (LPPOINT)&rcTab, 2);

    pHdr->rcDisplay = rcTab;

    //
    // Create both dialog boxes.
    //
    for (nCount = 0; nCount < NUM_TABS; nCount++) {
        pHdr->hDisplay[nCount] = CreateDialogIndirect(g_hInstance,
                                                      pHdr->pRes[nCount],
                                                      hMainDlg,
                                                      pHdr->pDlgProc[nCount]);
    }
}

TCHAR* 
GetRelativePath(
    TCHAR* pExeFile,
    TCHAR* pMatchFile
    )
/*++
    GetRelativePath

    Description:    Returns a relative path based on an EXE and a matching file.
                    The caller must free the memory using free.

--*/
{
    int     nLenExe = 0;
    int     nLenMatch = 0;
    TCHAR*  pExe    = NULL;
    TCHAR*  pMatch  = NULL;
    TCHAR*  pReturn = NULL;
    TCHAR   result[MAX_PATH] = { _T('\0') };
    TCHAR*  resultIdx = result;
    BOOL    bCommonBegin = FALSE; // Indicates if the paths have a common beginning

    //
    // Ensure that the beginning of the path matches between the two files
    //
    pExe = _tcschr(pExeFile, _T('\\'));
    pMatch = _tcschr(pMatchFile, _T('\\'));

    while (pExe && pMatch) {
        
        nLenExe = (int)(pExe - pExeFile);
        nLenMatch = (int)(pMatch - pMatchFile);

        if (nLenExe != nLenMatch) {
            break;
        }

        if (!(_tcsnicmp(pExeFile, pMatchFile, nLenExe) == 0)) {
            break;
        }

        bCommonBegin = TRUE;
        pExeFile = pExe + 1;
        pMatchFile = pMatch + 1;

        pExe = _tcschr(pExeFile, _T('\\'));
        pMatch = _tcschr(pMatchFile, _T('\\'));
    }

    //
    // Walk the path and put '..\' where necessary
    //
    if (bCommonBegin) {
        
        while (pExe) {

            lstrcpy(resultIdx, _T("..\\"));
            resultIdx = resultIdx + 3;
            pExeFile  = pExe + 1;
            pExe = _tcschr(pExeFile, _T('\\'));
        }

        lstrcpy(resultIdx, pMatchFile);
        
        pReturn = (TCHAR*)HeapAlloc(GetProcessHeap(),
                                    HEAP_ZERO_MEMORY,
                                    sizeof(TCHAR) * (lstrlen(result) + 1));
        
        if (NULL == pReturn) {
            return NULL;
        }

        lstrcpy(pReturn, result);

        return pReturn;
    }

    // the two paths don't have a common beginning,
    // and there is no relative path
    return NULL;
}

void 
SaveEntryToFile(
    HWND    hDlg,
    HWND    hEdit,
    LPCTSTR lpFileName
    )
/*++
    SaveEntryToFile

    Description:    Writes the XML out to a file.

--*/
{
    int     nLen = 0;
    DWORD   dwBytesWritten = 0;
    HANDLE  hFile = NULL;
    LPTSTR  lpData = NULL;
    TCHAR   szError[MAX_PATH];

    //
    // Determine how much space we need for the buffer, then allocate it.
    //
    nLen = GetWindowTextLength(hEdit);

    if (nLen) {

        lpData = (LPTSTR)HeapAlloc(GetProcessHeap(),
                                   HEAP_ZERO_MEMORY,
                                   nLen * 2 * sizeof(TCHAR));

        if (lpData == NULL) {
            LoadString(g_hInstance, IDS_BUFFER_ALLOC_FAIL, szError, MAX_PATH);
            MessageBox(hDlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
            return;
        }

        //
        // Get the text out of the text box and write it out to our file.
        // 
        GetWindowText(hEdit, lpData, nLen * 2);

        hFile = CreateFile(lpFileName,
                           GENERIC_WRITE,
                           0,
                           NULL,
                           CREATE_ALWAYS,
                           FILE_ATTRIBUTE_NORMAL,
                           NULL);
        
        if (hFile == INVALID_HANDLE_VALUE) {
            LoadString(g_hInstance, IDS_FILE_CREATE_FAIL, szError, MAX_PATH);
            MessageBox(hDlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
            goto Cleanup;
        }

        WriteFile(hFile, lpData, nLen * 2, &dwBytesWritten, NULL);

        CloseHandle(hFile);
        
    }

Cleanup:

    HeapFree(GetProcessHeap(), 0, lpData);
    
}

void 
DoFileSave(
    HWND hDlg
    )
/*++
    DoFileSave

    Description:    Displays the common dialog allowing for file save.

--*/
{
    
    int             nAnswer;
    TCHAR           szError[MAX_PATH];
    TCHAR           szFilter[MAX_PATH] = _T("");
    TCHAR           szTemp[MAX_PATH+1] = _T("");
    OPENFILENAME    ofn = {0};

    szTemp[0] = 0;

    LoadString(g_hInstance, IDS_SAVE_FILTER, szFilter, MAX_PATH);

    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hDlg;
    ofn.hInstance         = NULL;
    ofn.lpstrFilter       = szFilter;
    ofn.lpstrCustomFilter = (LPTSTR)NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 1;
    ofn.lpstrFile         = szTemp;
    ofn.nMaxFile          = sizeof(szTemp);
    ofn.lpstrTitle        = NULL;
    ofn.lpstrFileTitle    = NULL;
    ofn.nMaxFileTitle     = 0;
    ofn.lpstrInitialDir   = NULL;
    ofn.nFileOffset       = 0;
    ofn.nFileExtension    = 0;
    ofn.lpstrDefExt       = _T("xml");
    ofn.lCustData         = 0;
    ofn.Flags             = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | 
                            OFN_HIDEREADONLY  | OFN_OVERWRITEPROMPT;

    if (GetSaveFileName(&ofn)) {
        SaveEntryToFile(hDlg, GetDlgItem(hDlg, IDC_XML), szTemp);
    }
}

void
GetTopLevelWindowIntoView(
    HWND hwnd
    )
{
    RECT    rectWindow, rectScreen;
    int     nCx, nCy, nCxScreen, nCyScreen;
    int     dx = 0, dy = 0;
    HWND    hwndDesktop;

    if (GetWindowLong(hwnd, GWL_STYLE) & WS_CHILD) {
        return;
    }
    
    hwndDesktop = GetDesktopWindow();

    GetWindowRect(hwnd, &rectWindow);
    GetWindowRect(hwndDesktop, &rectScreen);

    nCx = rectWindow.right  - rectWindow.left;
    nCy = rectWindow.bottom - rectWindow.top;
    
    nCxScreen = rectScreen.right  - rectScreen.left;
    nCyScreen = rectScreen.bottom - rectScreen.top;

    //
    // Make it fix on the x coord.
    //
    if (rectWindow.left < rectScreen.left) {
        dx = rectScreen.left - rectWindow.left;

        rectWindow.left += dx;
        rectWindow.right += dx;
    }
    
    if (rectWindow.right > rectScreen.right) {
        if (nCx < nCxScreen) {
            dx = rectScreen.right - rectWindow.right;
            
            rectWindow.left += dx;
            rectWindow.right += dx;
        }
    }

    //
    // Make it fix on the y coord.
    //
    if (rectWindow.top < rectScreen.top) {
        dy = rectScreen.top - rectWindow.top;

        rectWindow.top += dy;
        rectWindow.bottom += dy;
    }
    
    if (rectWindow.bottom > rectScreen.bottom) {
        if (nCy < nCyScreen) {
            dy = rectScreen.bottom - rectWindow.bottom;
            
            rectWindow.top += dy;
            rectWindow.bottom += dy;
        }
    }

    if (dx != 0 || dy != 0) {
        MoveWindow(hwnd, rectWindow.left, rectWindow.top, nCx, nCy, TRUE);
    }
}

BOOL
CenterWindow(
    HWND hWnd
    )
/*++
    CenterWindow

    Description:    Centers the window specified in hWnd.

--*/
{
    RECT    rectWindow, rectParent, rectScreen;
    int     nCX, nCY;
    HWND    hParent;
    POINT   ptPoint;

    hParent =  GetParent(hWnd);
    
    if (hParent == NULL) {
        hParent = GetDesktopWindow();
    }

    GetWindowRect(hParent, &rectParent);
    GetWindowRect(hWnd, &rectWindow);
    GetWindowRect(GetDesktopWindow(), &rectScreen);

    nCX = rectWindow.right  - rectWindow.left;
    nCY = rectWindow.bottom - rectWindow.top;

    ptPoint.x = ((rectParent.right  + rectParent.left) / 2) - (nCX / 2);
    ptPoint.y = ((rectParent.bottom + rectParent.top ) / 2) - (nCY / 2);

    if (ptPoint.x < rectScreen.left) {
        ptPoint.x = rectScreen.left;
    }
    
    if (ptPoint.x > rectScreen.right  - nCX) {
        ptPoint.x = rectScreen.right  - nCX;
    }
    
    if (ptPoint.y < rectScreen.top) {
        ptPoint.y = rectScreen.top;
    }
    
    if (ptPoint.y > rectScreen.bottom - nCY) {
        ptPoint.y = rectScreen.bottom - nCY;
    }

    if (GetWindowLong(hWnd, GWL_STYLE) & WS_CHILD) {
        ScreenToClient(hParent, (LPPOINT)&ptPoint);
    }

    if (!MoveWindow(hWnd, ptPoint.x, ptPoint.y, nCX, nCY, TRUE)) {
        return FALSE;
    }

    return TRUE;
}

void
ReplaceCmdLine(
    PFIX   pFix,
    TCHAR* pszNewCmdLine
    )
/*++
    ReplaceCmdLine

    Description:    Replaces the command line for a shim DLL.

--*/
{
    TCHAR   szError[MAX_PATH];

    if (pFix->pszCmdLine != NULL) {
        HeapFree(GetProcessHeap(), 0, pFix->pszCmdLine);
        pFix->pszCmdLine = NULL;
    }

    if (pszNewCmdLine == NULL) {
        return;
    
    } else if ((*pszNewCmdLine == '"') && (_tcslen(pszNewCmdLine)==1)) {
        LoadString(g_hInstance, IDS_INVALID_CMD_LINE, szError, MAX_PATH);
        MessageBox(g_hDlg, szError, g_szAppTitle, MB_OK | MB_ICONEXCLAMATION);
        return;
    }
    
    pFix->pszCmdLine = (TCHAR*)HeapAlloc(GetProcessHeap(),
                                         HEAP_ZERO_MEMORY,
                                         sizeof(TCHAR) * (lstrlen(pszNewCmdLine) + 1));

    if (pFix->pszCmdLine != NULL) {
        lstrcpy(pFix->pszCmdLine, pszNewCmdLine);
    } else {
        LogMsg(_T("[ReplaceCmdLine] failed to replace the cmd line for \"%s\"\n"),
               pFix->pszName);
    }
}

void
DeselectAllShims(
    HWND hdlg
    )
/*++
    DeselectAllShims

    Description:    Removes selections for all shims listed.

--*/
{
    int     cShims, nIndex;
    LVITEM  lvi;
    UINT    uState;

    //
    // Walk all the shims in the list view and deselect them.
    //
    ZeroMemory(&lvi, sizeof(lvi));

    cShims = ListView_GetItemCount(g_hwndShimList);

    for (nIndex = 0; nIndex < cShims; nIndex++) {

        PFIX pFix;
        
        lvi.iItem     = nIndex;
        lvi.mask      = LVIF_STATE | LVIF_PARAM;
        lvi.stateMask = LVIS_STATEIMAGEMASK;
        
        ListView_GetItem(g_hwndShimList, &lvi);

        pFix = (PFIX)lvi.lParam;

        //
        // Clear the check box, removes the 'X', clear the command line,
        // and clear the modules.
        //
        ListView_SetItemText(g_hwndShimList, nIndex, 1, _T(""));
        ListView_SetItemText(g_hwndShimList, nIndex, 2, _T(""));
        ListView_SetCheckState(g_hwndShimList, nIndex, FALSE);
        ReplaceCmdLine(pFix, NULL);
        BuildModuleListForShim(pFix, BML_DELFRLISTVIEW);
    }
    
    //
    // Update the count of selected shims.
    //
    SetTimer(hdlg, ID_COUNT_SHIMS, 100, NULL);
}

void
AddMatchingFile(
    HWND    hdlg,
    LPCTSTR pszFullPath,
    LPCTSTR pszRelativePath,
    BOOL    bMainEXE
    )
/*++
    AddMatchingFile

    Description:    Adds a matching file and it's attributes to the tree.

--*/
{
    PATTRINFO      pAttrInfo;
    TVINSERTSTRUCT is;
    HTREEITEM      hParent;
    DWORD          i;
    DWORD          dwAttrCount;
    TCHAR          szItem[MAX_PATH];

    //
    // Call the attribute manager to get all the attributes for this file.
    //
    SdbGetFileAttributes(pszFullPath, &pAttrInfo, &dwAttrCount);

    is.hParent      = TVI_ROOT;
    is.hInsertAfter = TVI_LAST;
    is.item.lParam  = (LPARAM)pAttrInfo;
    is.item.mask    = TVIF_TEXT | TVIF_PARAM;
    is.item.pszText = (LPTSTR)pszRelativePath;

    hParent = TreeView_InsertItem(g_hwndFilesTree, &is);

    is.hParent = hParent;

    is.item.mask    = TVIF_TEXT | TVIF_STATE | TVIF_PARAM | TVIF_IMAGE | TVIF_SELECTEDIMAGE;
    is.item.pszText = szItem;

    is.item.iImage         = 0;
    is.item.iSelectedImage = 1;
    
    //
    // By default the attributes are not selected. To have them selected
    // by default you need to replace the following 1 with 2.
    //
    is.item.state          = INDEXTOSTATEIMAGEMASK(1);
    is.item.stateMask      = TVIS_STATEIMAGEMASK;

    //
    // Loop through all the attributes and show the ones that are available.
    //
    for (i = 0; i < dwAttrCount; i++) {

        if (!SdbFormatAttribute(&pAttrInfo[i], szItem, MAX_PATH)) {
            continue;
        }
        
        //
        // EXETYPE is a bogus attribute. Don't show it!
        //
        is.item.lParam = i;
        TreeView_InsertItem(g_hwndFilesTree, &is);
    }

    TreeView_Expand(g_hwndFilesTree, hParent, TVE_EXPAND);
}

void
BrowseForApp(
    HWND hdlg
    )
/*++
    BrowseForApp

    Description:    Browse for the main executable for which a shim
                    will be applied.
--*/
{
    TCHAR        szFilter[MAX_PATH] = _T("");
    TCHAR        szTitle[MAX_PATH] = _T("");
    OPENFILENAME ofn = {0};
    
    g_szBinary[0] = 0;

    LoadString(g_hInstance, IDS_BROWSE_FILTER, szFilter, MAX_PATH);
    LoadString(g_hInstance, IDS_BROWSE_TITLE, szTitle, MAX_PATH);

    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hdlg;
    ofn.hInstance         = NULL;
    ofn.lpstrFilter       = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile         = g_szBinary;
    ofn.nMaxFile          = MAX_PATH;
    ofn.lpstrFileTitle    = g_szShortName;
    ofn.nMaxFileTitle     = 128;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrTitle        = szTitle;
    ofn.Flags             = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt       = _T("exe");

    if (GetOpenFileName(&ofn)) {

        TCHAR szMainEXE[128];

        // the parent exe defaults to the same as the EXE
        lstrcpy(g_szParentExeName, g_szShortName);
        lstrcpy(g_szParentExeFullPath, g_szBinary);
        g_bSelectedParentExe = FALSE;

        SetDlgItemText(hdlg, IDC_BINARY, g_szBinary);

        EnableWindow(GetDlgItem(hdlg, IDC_ADD_MATCHING), TRUE);
        EnableWindow(GetDlgItem(hdlg, IDC_RUN), TRUE);
        EnableWindow(GetDlgItem(hdlg, IDC_CREATEFILE), TRUE);
        EnableWindow(GetDlgItem(hdlg, IDC_SHOWXML), TRUE);

        TreeView_DeleteAllItems(g_hwndFilesTree);

        wsprintf(szMainEXE, _T("Main executable (%s)"), g_szShortName);

        AddMatchingFile(hdlg, g_szBinary, szMainEXE, TRUE);
    }
}

void
PromptAddMatchingFile(
    HWND hdlg
    )
/*++
    PromptAddMatchingFile

    Description:    Show the open file dialog to allow the user
                    to add a matching file.
--*/
{
    TCHAR        szFullPath[MAX_PATH+1] = _T("");
    TCHAR        szShortName[128] = _T("");
    TCHAR        szRelativePath[MAX_PATH] = _T("");
    TCHAR        szFilter[MAX_PATH] = _T("");
    TCHAR        szTitle[MAX_PATH] = _T("");
    TCHAR        szParentTitle[MAX_PATH] = _T("");
    TCHAR        szInitialPath[MAX_PATH] = _T("");
    TCHAR        szDrive[_MAX_DRIVE] = _T("");
    TCHAR        szDir[_MAX_DIR] = _T("");
    TCHAR*       pMatch = NULL;
    TCHAR        szError[MAX_PATH];
    OPENFILENAME ofn = {0};

    szInitialPath[0] = 0;

    LoadString(g_hInstance, IDS_MATCH_FILTER, szFilter, MAX_PATH);
    LoadString(g_hInstance, IDS_MATCH_TITLE, szTitle, MAX_PATH);

    if (g_szParentExeFullPath[0]) {
        _tsplitpath(g_szParentExeFullPath, szDrive, szDir, NULL, NULL);
        lstrcpy(szInitialPath, szDrive);
        lstrcat(szInitialPath, szDir);
    }

    ofn.lStructSize       = sizeof(OPENFILENAME);
    ofn.hwndOwner         = hdlg;
    ofn.hInstance         = NULL;
    ofn.lpstrFilter       = szFilter;
    ofn.lpstrCustomFilter = NULL;
    ofn.nMaxCustFilter    = 0;
    ofn.nFilterIndex      = 0;
    ofn.lpstrFile         = szFullPath;
    ofn.nMaxFile          = MAX_PATH;
    ofn.lpstrFileTitle    = szShortName;
    ofn.nMaxFileTitle     = 128;
    ofn.lpstrInitialDir   = NULL;
    ofn.lpstrTitle        = szTitle;
    ofn.Flags             = OFN_FILEMUSTEXIST | OFN_PATHMUSTEXIST | OFN_HIDEREADONLY;
    ofn.lpstrDefExt       = _T("EXE");

    if (GetOpenFileName(&ofn)) {
        //
        // Determine if the matching file is on the same drive
        // as the EXE that was selected.
        //
        if (!PathIsSameRoot(szFullPath,
                            g_szParentExeFullPath) && !g_bSelectedParentExe) {

            TCHAR szParentFile[MAX_PATH];

            //
            // Prompt the user for the parent EXE.
            //
            szParentFile[0] = 0;
            szInitialPath[0] = 0;
            
            if (szFullPath[0]) {
                _tsplitpath(szFullPath, szDrive, szDir, NULL, NULL);
                lstrcpy(szInitialPath, szDrive);
                lstrcat(szInitialPath, szDir);
            }

            LoadString(g_hInstance, IDS_PARENT_TITLE, szParentTitle, MAX_PATH);

            ofn.lpstrTitle = szParentTitle;
            ofn.lpstrFile  = szParentFile;
            ofn.nMaxFile   = sizeof(szParentFile);

            if (GetOpenFileName(&ofn) == TRUE) { 
                lstrcpy(g_szParentExeName, szShortName);
                lstrcpy(g_szParentExeFullPath, szParentFile);
                g_bSelectedParentExe = TRUE;
            }
        }
        
        //
        // Check the drive letters to see which drive the match file is on
        // then calculate a relative path to the matching file.
        //
        if (PathIsSameRoot(szFullPath, g_szParentExeFullPath)) {
            pMatch = GetRelativePath(g_szParentExeFullPath, szFullPath);
        
        } else if (PathIsSameRoot(szFullPath, g_szBinary)) {
            pMatch = GetRelativePath(g_szBinary, szFullPath);

        } else {
            LoadString(g_hInstance, IDS_MATCH_PATH_NOT_RELATIVE, szError, MAX_PATH);
            MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            return;
        }

        if (pMatch) {
            //
            // Finally add the maching file and free the memory
            //
            AddMatchingFile(hdlg, szFullPath, pMatch, FALSE);
            
            HeapFree(GetProcessHeap(), 0, pMatch);
        }
    }
}

void
ShowAvailableFixes(
    HWND hList
    )
/*++
    ShowAvailableFixes

    Description:    Query the shim database and populate the
                    shim list with all the available shims.
--*/
{
    LVITEM lvitem;
    PFIX   pFix;
    TCHAR  szError[MAX_PATH];
    UINT   uCount = 0;

    g_pFixHead = ReadFixesFromSdb(_T("sysmain.sdb"), g_bAllShims);
    
    if (g_pFixHead == NULL) {
        LoadString(g_hInstance, IDS_SDB_READ_FAIL, szError, MAX_PATH); 
        MessageBox(NULL, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
        return;
    }

    //
    // Walk the list and add all the fixes to the list view.
    //
    pFix = g_pFixHead;

    while (pFix != NULL) {
        
        if (pFix->bLayer) {
            LPARAM lInd;
            
            lInd = SendMessage(g_hwndListLayers, LB_ADDSTRING, 0, (LPARAM)pFix->pszName);
            SendMessage(g_hwndListLayers, LB_SETITEMDATA, lInd, (LPARAM)pFix);
        } else {
            lvitem.mask      = LVIF_TEXT | LVIF_PARAM | LVIF_STATE;
            lvitem.lParam    = (LPARAM)pFix;
            lvitem.pszText   = pFix->pszName;
            lvitem.iItem     = ListView_GetItemCount(g_hwndShimList);
            lvitem.iSubItem  = 0;
            lvitem.state     = INDEXTOSTATEIMAGEMASK(1);
            lvitem.stateMask = LVIS_STATEIMAGEMASK;

            ListView_InsertItem(hList, &lvitem);
        }

        pFix = pFix->pNext;
    }
}

BOOL
InstallSDB(
    TCHAR* pszFileName,
    BOOL   fInstall
    )
/*++
    InstallSDB

    Description:    Launch SDBInst to install or uninstall
                    the specified SDB.

--*/
{
    TCHAR               szCmd[MAX_PATH];
    TCHAR               szExePath[MAX_PATH];
    TCHAR*              pExt = NULL;
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;

    GetSystemDirectory(szExePath, MAX_PATH);

    _tcscat(szExePath, _T("\\sdbinst.exe"));

    if (GetFileAttributes(szExePath) == -1) {
        return FALSE;
    }

    wsprintf(szCmd,
             fInstall ? _T("%s -q %s") : _T("%s -q -u %s"),
             szExePath,
             pszFileName);

    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    if (!CreateProcess(NULL,
                       szCmd,
                       NULL,
                       NULL,
                       FALSE,
                       NORMAL_PRIORITY_CLASS | CREATE_NO_WINDOW,
                       NULL,
                       NULL,
                       &si,
                       &pi)) {

        LogMsg(_T("[InstallSDB] CreateProcess \"%s\" failed 0x%X\n"),
               szCmd, GetLastError());
        return FALSE;
    }

    // Wait for SDBInst to complete it's work.
    WaitForSingleObject(pi.hProcess, INFINITE);

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    return TRUE;
}

void
CreateSupportForApp(
    HWND hdlg
    )
/*++
    CreateSupportForApp

    Description:    Build an SDB for the application and offer the user
                    the chance to install it.
--*/
{
    BOOL    bok;
    TCHAR   szFileCreated[MAX_PATH];
    TCHAR   szError[MAX_PATH];
    TCHAR   szTemp[MAX_PATH];
    int     nAnswer;
    
    bok = CollectFix(g_hwndListLayers,
                     g_hwndShimList,
                     g_hwndFilesTree,
                     g_szShortName,
                     g_szBinary,
                     (g_nCrtTab == 0 ? CFF_USELAYERTAB : 0) |
                     (g_fW2K ? CFF_ADDW2KSUPPORT : 0),
                     szFileCreated);
    
    if (!bok) {
        LoadString(g_hInstance, IDS_FIX_CREATE_FAIL, szError, MAX_PATH);
        MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
    } else {
        LoadString(g_hInstance, IDS_CREATE_FIX, szTemp, MAX_PATH);
        wsprintf(szError, szTemp, szFileCreated);
        
        nAnswer = MessageBox(hdlg, szError, g_szAppTitle, MB_YESNO | MB_ICONQUESTION);

        if (IDYES == nAnswer) {
            bok = InstallSDB(szFileCreated, TRUE);

            if (!bok) {
                LoadString(g_hInstance, IDS_INSTALL_FIX_FAIL, szError, MAX_PATH);
                MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
            } else {
                LoadString(g_hInstance, IDS_INSTALL_FIX_OK, szError, MAX_PATH);
                MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONINFORMATION);
            }
        }
    }
}

BOOL
ShowXML(
    HWND hdlg
    )
/*++
    ShowXML

    Description:    Show the XML for the current selections.

--*/
{
    BOOL    bok;
    TCHAR   szError[MAX_PATH];

    bok = CollectFix(g_hwndListLayers,
                     g_hwndShimList,
                     g_hwndFilesTree,
                     g_szShortName,
                     g_szBinary,
                     CFF_SHOWXML |
                     (g_nCrtTab == 0 ? CFF_USELAYERTAB : 0) |
                     (g_fW2K ? CFF_ADDW2KSUPPORT : 0),
                     NULL);

    if (!bok) {
        LoadString(g_hInstance, IDS_TOO_MANY_FILES, szError, MAX_PATH);
        MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONEXCLAMATION);
    }
    
    return (bok);
}

void
RunTheApp(
    HWND hdlg
    )
/*++
    RunTheApp

    Description:    Run the selected app.

--*/
{
    STARTUPINFO         si;
    PROCESS_INFORMATION pi;
    TCHAR               szFileCreated[MAX_PATH];
    TCHAR               szCmdLine[MAX_PATH];
    TCHAR               szError[MAX_PATH];
    TCHAR               szRun[MAX_PATH];
    TCHAR*              pszCmd;
    TCHAR*              pszDir;
    TCHAR*              psz;
    BOOL                bok;

    //
    // Cleanup for the previous app.
    //
    CleanupSupportForApp(g_szShortName);

    bok = CollectFix(g_hwndListLayers,
                     g_hwndShimList,
                     g_hwndFilesTree,
                     g_szShortName,
                     g_szBinary,
                     CFF_SHIMLOG |
                     CFF_APPENDLAYER |
                     (g_nCrtTab == 0 ? CFF_USELAYERTAB : 0) |
                     (g_fW2K ? CFF_ADDW2KSUPPORT : 0),
                     szFileCreated);
    
    if (!bok) {
        LoadString(g_hInstance, IDS_ADD_SUPPORT_FAIL, szError, MAX_PATH);
        MessageBox(hdlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
        return;
    }

    //
    // We need to install the fix for them.
    //
    if (!(InstallSDB(szFileCreated, TRUE))) {
        LoadString(g_hInstance, IDS_INSTALL_FIX_FAIL, szError, MAX_PATH);
        MessageBox(g_hDlg, szError, g_szAppTitle, MB_OK | MB_ICONERROR);
        return;
    }
    
    ZeroMemory(&si, sizeof(si));
    si.cb = sizeof(si);

    //
    // Try to run the app.
    //
    GetDlgItemText(hdlg, IDC_CMD_LINE, szCmdLine, MAX_PATH);

    if (szCmdLine[0] == 0) {
        wsprintf(szRun, _T("\"%s\""), g_szBinary);
    } else {
        wsprintf(szRun, _T("\"%s\" %s"), g_szBinary, szCmdLine);
    }

    pszCmd = szRun;
    pszDir = g_szBinary;

    //
    // We need to change the current directory or some
    // apps won't run.
    //
    psz = pszDir + lstrlen(pszDir) - 1;

    while (psz > pszDir && *psz != _T('\\')) {
        psz--;
    }

    if (psz > pszDir) {
        *psz = 0;
        SetCurrentDirectory(pszDir);
        *psz = _T('\\');
    }

    LogMsg(_T("[RunTheApp] : %s\n"), pszCmd);
    
    if (!CreateProcess(NULL,
                       pszCmd,
                       NULL,
                       NULL,
                       FALSE,
                       NORMAL_PRIORITY_CLASS,
                       NULL,
                       NULL,
                       &si,
                       &pi)) {

        LogMsg(_T("[RunTheApp] CreateProcess failed 0x%X\n"), GetLastError());
        return;
    }

    CloseHandle(pi.hThread);
    CloseHandle(pi.hProcess);

    //
    // Save this SDB for later so we can remove it.
    //
    lstrcpy(g_szSDBToDelete, szFileCreated);
}

void
ExpandCollapseDialog(
    HWND hdlg,
    BOOL bHide
    )
/*++
    ExpandCollapseDialog

    Description:    Change the current view of the dialog.

--*/
{
    TCHAR   szSimple[64];
    TCHAR   szAdvanced[64];
    int     i, nShow;
    DWORD   arrId[] = {IDC_ADD_MATCHING,
                       IDC_FILE_ATTRIBUTES_STATIC,
                       IDC_ATTRIBUTES,
                       IDC_CREATEFILE,
                       0};

    if (!bHide) {
        SetWindowPos(hdlg, NULL, 0, 0,
                     g_rcDlgBig.right - g_rcDlgBig.left,
                     g_rcDlgBig.bottom - g_rcDlgBig.top,
                     SWP_NOMOVE | SWP_NOZORDER);
        nShow = SW_SHOW;
        g_bSimpleEdition = FALSE;
        LoadString(g_hInstance, IDS_SIMPLE_TEXT, szSimple, 64);
        SetDlgItemText(hdlg, IDC_DETAILS, szSimple);
        SendDlgItemMessage(hdlg, IDC_CREATEFILE, BM_SETCHECK, BST_CHECKED, 0);

        //
        // Make sure the dialog is in view.
        //
        GetTopLevelWindowIntoView(hdlg);
    } else {
        nShow = SW_HIDE;
        g_bSimpleEdition = TRUE;
        LoadString(g_hInstance, IDS_ADVANCED_TEXT, szAdvanced, 64);
        SetDlgItemText(hdlg, IDC_DETAILS, szAdvanced);
        SendDlgItemMessage(hdlg, IDC_CREATEFILE, BM_SETCHECK, BST_UNCHECKED, 0);
    }

    for (i = 0; arrId[i] != 0; i++) {
        ShowWindow(GetDlgItem(hdlg, arrId[i]), nShow);
    }

    if (bHide) {
        SetWindowPos(hdlg, NULL, 0, 0,
                     g_rcDlgSmall.right - g_rcDlgSmall.left,
                     g_rcDlgSmall.bottom - g_rcDlgSmall.top,
                     SWP_NOMOVE | SWP_NOZORDER);
    }
}

void
LayerChanged(
    HWND hdlg
    )
/*++
    LayerChanged

    Description:    Changing the layer has the effect of selecting the
                    shims that the layer consists of.
--*/
{
    LRESULT   lSel;
    PFIX      pFix;
    LVITEM    lvi;
    int       nIndex, cShims = 0;

    lSel = SendMessage(g_hwndListLayers, LB_GETCURSEL, 0, 0);

    if (lSel == LB_ERR) {
        LogMsg(_T("[LayerChanged] No layer selected.\n"));
        return;
    }
    
    pFix = (PFIX)SendMessage(g_hwndListLayers, LB_GETITEMDATA, lSel, 0);

    if (pFix->parrShim == NULL) {
        LogMsg(_T("[LayerChanged] No array of DLLs.\n"));
        return;
    }

    // Remove any prior selections.
    DeselectAllShims(g_hFixesDlg);

    //
    // Loop through all the items in the shim list and make the
    // appropriate selections.
    //
    cShims = ListView_GetItemCount(g_hwndShimList);

    for (nIndex = 0; nIndex < cShims; nIndex++) {

        PFIX  pFixItem;
        TCHAR szText[1024];
        int   nInd = 0;
        
        lvi.mask     = LVIF_PARAM;
        lvi.iItem    = nIndex;
        lvi.iSubItem = 0; 

        ListView_GetItem(g_hwndShimList, &lvi);
        
        pFixItem = (PFIX)lvi.lParam;

        //
        // See if this shim DLL is in the array for the selected layer.
        //
        while (pFix->parrShim[nInd] != NULL) {
            
            if (pFix->parrShim[nInd] == pFixItem) {
                break;
            }
            
            nInd++;
        }

        //
        // Put a check next to this shim DLL. If he has a command line,
        // put an 'X' in the CmdLine subitem.
        //
        if (pFix->parrShim[nInd] != NULL) {
            ListView_SetCheckState(g_hwndShimList, nIndex, TRUE);
        } else {
            ListView_SetCheckState(g_hwndShimList, nIndex, FALSE);
        }

        if (pFix->parrCmdLine[nInd] != NULL) {
            ReplaceCmdLine(pFixItem, pFix->parrCmdLine[nInd]);
            ListView_SetItemText(g_hwndShimList, nIndex, 1, _T("X"));
        }

        ListView_SetItem(g_hwndShimList, &lvi);
    }
    
    //
    // Update the count of selected shims.
    //
    SetTimer(g_hFixesDlg, ID_COUNT_SHIMS, 100, NULL);
}

BOOL
InitMainDialog(
    HWND hdlg
    )
/*++
    InitMainDialog

    Description:    Init routine called during WM_INITDIALOG for
                    the main dialog of QFixApp.
--*/
{
    HICON      hIcon;
    RECT       rcList, rcTree;
    HIMAGELIST hImage;
    TCHAR      szText[MAX_PATH];

    //
    // Initialize globals.
    //
    g_szParentExeFullPath[0] = 0;
    g_szBinary[0] = 0;
    g_hDlg = hdlg;

    //
    // The dialog has two views. Calculate the size of the smaller
    // view and show the simpler view by default.
    //
    GetWindowRect(hdlg, &g_rcDlgBig);

    GetWindowRect(GetDlgItem(hdlg, IDC_ATTRIBUTES), &rcList);
    GetWindowRect(GetDlgItem(hdlg, IDC_TAB_FIXES), &rcTree);

    g_rcDlgSmall.left   = g_rcDlgBig.left;
    g_rcDlgSmall.top    = g_rcDlgBig.top;
    g_rcDlgSmall.bottom = g_rcDlgBig.bottom;
    g_rcDlgSmall.right  = g_rcDlgBig.right -
                            (rcList.right - rcList.left) -
                            (rcList.left - rcTree.right);

    ExpandCollapseDialog(hdlg, TRUE);

    CenterWindow(hdlg);

    //
    // Disable a bunch of controls.
    //
    EnableWindow(GetDlgItem(hdlg, IDC_ADD_MATCHING), FALSE);
    EnableWindow(GetDlgItem(hdlg, IDC_REMOVE_MATCHING), FALSE);
    EnableWindow(GetDlgItem(hdlg, IDC_RUN), FALSE);
    EnableWindow(GetDlgItem(hdlg, IDC_CREATEFILE), FALSE);
    EnableWindow(GetDlgItem(hdlg, IDC_SHOWXML), FALSE);

    //
    // Show the app icon.
    //
    hIcon = LoadIcon(g_hInstance, MAKEINTRESOURCE(IDI_ICON));

    SetClassLongPtr(hdlg, GCLP_HICON, (LONG_PTR)hIcon);

    g_hwndTab        = GetDlgItem(hdlg, IDC_TAB_FIXES);
    g_hwndFilesTree  = GetDlgItem(hdlg, IDC_ATTRIBUTES);

    //
    // Set up the tab control.
    //
    InitTabs(hdlg, g_hwndTab);
    
    hImage = ImageList_LoadImage(g_hInstance,
                                 MAKEINTRESOURCE(IDB_BMP_CHECK),
                                 16,
                                 0,
                                 CLR_DEFAULT,
                                 IMAGE_BITMAP,
                                 LR_LOADTRANSPARENT);

    if (hImage != NULL) {
        TreeView_SetImageList(g_hwndFilesTree, hImage, TVSIL_STATE);
    } else {
        LogMsg(_T("[InitMainDialog] Failed to load imagelist\n"));
    }

    //
    // Set the text for the link window.
    //
    LoadString(g_hInstance,
               g_fW2K ? IDS_W2K_LINK : IDS_XP_LINK,
               szText,
               MAX_PATH);
    SetDlgItemText(g_hDlg, IDC_DOWNLOAD_WU, szText);

    //
    // Try selecting the Win95 layer.
    //
    SendMessage(g_hwndListLayers, LB_SELECTSTRING, (WPARAM)(-1), (LPARAM)_T("Win95"));

    LayerChanged(hdlg);

    TabCtrl_SetCurFocus(g_hwndTab, 0);
    ShowWindow(g_hLayersDlg, SW_SHOWNORMAL);

    return TRUE;
}

void
FileTreeToggleSelection(
    HTREEITEM hItem,
    int       uMode
    )
/*++
    FileTreeToggleSelection

    Description:    Changes the selection on the attributes tree.

--*/
{
    UINT   State;
    TVITEM item;

    switch (uMode) 
    {
        case uSelect:
            State = INDEXTOSTATEIMAGEMASK(2);
            break;
            
        case uDeselect:
            State = INDEXTOSTATEIMAGEMASK(1);
            break;
    
        case uReverse:
        {
            item.mask      = TVIF_HANDLE | TVIF_STATE;
            item.hItem     = hItem;
            item.stateMask = TVIS_STATEIMAGEMASK;
        
            TreeView_GetItem(g_hwndFilesTree, &item);
        
            State = item.state & TVIS_STATEIMAGEMASK;
        
            if (State) {
                if (((State >> 12) & 0x03) == 2) {
                    State = INDEXTOSTATEIMAGEMASK(1);
                } else {
                    State = INDEXTOSTATEIMAGEMASK(2);
                }
            }
            break;
        }
    }
    
    item.mask      = TVIF_HANDLE | TVIF_STATE;
    item.hItem     = hItem;
    item.state     = State;
    item.stateMask = TVIS_STATEIMAGEMASK;

    TreeView_SetItem(g_hwndFilesTree, &item);
}

void
SelectAttrsInTree(
    BOOL fSelect
    )
/*++
    SelectAttrsInTree

    Description:    Walks each attribute in tree and reverses it's selection.
    
--*/
{
    HTREEITEM hItem, hChildItem;
    
    hItem = TreeView_GetSelection(g_hwndFilesTree);
    
    hChildItem = TreeView_GetChild(g_hwndFilesTree, hItem);

    FileTreeToggleSelection(hChildItem, fSelect ? uSelect : uDeselect);

    while (hChildItem) {
        hChildItem = TreeView_GetNextSibling(g_hwndFilesTree, hChildItem);
        FileTreeToggleSelection(hChildItem, fSelect ? uSelect : uDeselect);
    }
}

void
ShimListToggleSelection(
    int nItem,
    int uMode
    )
/*++
    ShimListToggleSelection

    Description:    Changes the selection on the shim list.

--*/
{
    UINT    uState;

    switch (uMode) 
    {
        case uSelect:
            ListView_SetCheckState(g_hwndShimList, nItem, TRUE);
            break;
            
        case uDeselect:
            ListView_SetCheckState(g_hwndShimList, nItem, FALSE);
            break;
    
        case uReverse:
            
            uState = ListView_GetItemState(g_hwndShimList,
                                           nItem,
                                           LVIS_STATEIMAGEMASK);

            if (uState) {
                if (((uState >> 12) & 0x03) == 2) {
                    uState = INDEXTOSTATEIMAGEMASK(2);
                } else {
                    uState = INDEXTOSTATEIMAGEMASK(1);
                }
            }

            ListView_SetItemState(g_hwndShimList, nItem, uState,
                                  LVIS_STATEIMAGEMASK);

            break;
    }
}

void
HandleTabNotification(
    HWND   hdlg,
    LPARAM lParam
    )
/*++
    HandleTabNotification

    Description:    Handle all the notifications we care about for the tab.

--*/
{
    LPNMHDR pnm = (LPNMHDR)lParam;
    int     ind = 0;

    switch (pnm->code) {

    case TCN_SELCHANGE:
    {
        int nSel;

        DLGHDR *pHdr = (DLGHDR*)GetWindowLongPtr(hdlg, DWLP_USER); 

        nSel = TabCtrl_GetCurSel(pHdr->hTab);

        if (-1 == nSel) {
            break;
        }

        g_nCrtTab = nSel;

        if (nSel == 0) {
            ShowWindow(pHdr->hDisplay[1], SW_HIDE);
            ShowWindow(pHdr->hDisplay[0], SW_SHOW);
        } else {
            ShowWindow(pHdr->hDisplay[0], SW_HIDE);
            ShowWindow(pHdr->hDisplay[1], SW_SHOW);
        }
        
        break;
    }

    default:
        break;
    }
}

INT_PTR CALLBACK
OptionsDlgProc(
    HWND   hdlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
/*++
    OptionsDlgProc

    Description:    Handles messages for the options dialog.

--*/
{
    int wCode = LOWORD(wParam);
    int wNotifyCode = HIWORD(wParam);

    switch (uMsg) {
    case WM_INITDIALOG:
    {
        PFIX    pFix;
        TCHAR   szTitle[MAX_PATH];
        TCHAR   szTemp[MAX_PATH];
        TCHAR   szType[64];
        TCHAR   szModuleName[128];

        pFix = (PFIX)lParam;

        LoadString(g_hInstance, IDS_MOD_TYPE, szType, 64);
        LoadString(g_hInstance, IDS_MOD_NAME, szModuleName, 128);
        LoadString(g_hInstance, IDS_OPTIONS_TITLE, szTemp, MAX_PATH);
        
        CenterWindow(hdlg);

        SetWindowLongPtr(hdlg, DWLP_USER, lParam);
        
        EnableWindow(GetDlgItem(hdlg, IDC_REMOVE), FALSE);

        g_hwndModuleList = GetDlgItem(hdlg, IDC_MOD_LIST);

        InsertListViewColumn(g_hwndModuleList, szType, FALSE, 0, 75);
        InsertListViewColumn(g_hwndModuleList, szModuleName, FALSE, 1, 115);

        ListView_SetExtendedListViewStyle(g_hwndModuleList, LVS_EX_FULLROWSELECT);

        wsprintf(szTitle, szTemp, pFix->pszName);
        
        SetWindowText(hdlg, szTitle);
        
        if (NULL != pFix->pszCmdLine) {
            SetDlgItemText(hdlg, IDC_SHIM_CMD_LINE, pFix->pszCmdLine);
        }
        
        CheckDlgButton(hdlg, IDC_INCLUDE, BST_CHECKED);

        // Add any modules to the list view.
        BuildModuleListForShim(pFix, BML_ADDTOLISTVIEW);

        break;
    }
    
    case WM_NOTIFY:
        HandleModuleListNotification(hdlg, lParam);
        break;

    case WM_COMMAND:
        switch (wCode) {

        case IDC_ADD:
        {
            TCHAR   szModName[MAX_PATH];
            TCHAR   szError[MAX_PATH];
            LVITEM  lvi;
            UINT    uInclude, uExclude;

            GetDlgItemText(hdlg, IDC_MOD_NAME, szModName, MAX_PATH);

            if (*szModName == 0) {
                LoadString(g_hInstance, IDS_NO_MOD, szError, MAX_PATH);
                MessageBox(hdlg, szError, g_szAppTitle, MB_ICONEXCLAMATION | MB_OK);
                SetFocus(GetDlgItem(hdlg, IDC_MOD_NAME));
                break;
            }

            uInclude = IsDlgButtonChecked(hdlg, IDC_INCLUDE);
            uExclude = IsDlgButtonChecked(hdlg, IDC_EXCLUDE);

            if ((BST_CHECKED == uInclude) || (BST_CHECKED == uExclude)) {
                AddModuleToListView(szModName, uInclude);
                SetDlgItemText(hdlg, IDC_MOD_NAME, _T(""));
                SetFocus(GetDlgItem(hdlg, IDC_MOD_NAME));
            } else {
                LoadString(g_hInstance, IDS_NO_INCEXC, szError, MAX_PATH);
                MessageBox(hdlg, szError, g_szAppTitle, MB_ICONEXCLAMATION | MB_OK);
                SetFocus(GetDlgItem(hdlg, IDC_INCLUDE));
                break;
            }
            break;
            
        }
        case IDC_REMOVE:
        {   int nIndex;

            nIndex = ListView_GetSelectionMark(g_hwndModuleList);

            ListView_DeleteItem(g_hwndModuleList, nIndex);

            EnableWindow(GetDlgItem(hdlg, IDC_REMOVE), FALSE);

            SetFocus(GetDlgItem(hdlg, IDC_MOD_NAME));

            break;
        }
        case IDOK:
        {
            PFIX  pFix;
            TCHAR szCmdLine[1024] = _T("");
            
            pFix = (PFIX)GetWindowLongPtr(hdlg, DWLP_USER);

            GetDlgItemText(hdlg, IDC_SHIM_CMD_LINE, szCmdLine, 1023);

            if (*szCmdLine != 0) {
                ReplaceCmdLine(pFix, szCmdLine);
            } else {
                ReplaceCmdLine(pFix, NULL);
            }
            
            // Retrieve any modules from the list view.
            BuildModuleListForShim(pFix, BML_GETFRLISTVIEW);
            
            EndDialog(hdlg, TRUE);
            break;
        }
        case IDCANCEL:
            EndDialog(hdlg, FALSE);
            break;

        default:
            return FALSE;
        }
        break;

    default:
        return FALSE;
    }

    return TRUE;
}

INT_PTR CALLBACK
MsgBoxDlgProc(
    HWND   hdlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
/*++
    MsgBoxDlgProc

    Description:    Displays a message box dialog so we can use the hyperlink.

--*/
{
    int wCode = LOWORD(wParam);
    int wNotifyCode = HIWORD(wParam);

    switch (uMsg) {
    
    case WM_INITDIALOG:
    {
        TCHAR   szLink[MAX_PATH];
        UINT    uNoSDB;

        uNoSDB = (UINT)lParam;
        
        CenterWindow(hdlg);

        //
        // Use the parameter to determine what text to display.
        //
        if (uNoSDB) {
            LoadString(g_hInstance, IDS_W2K_NO_SDB, szLink, MAX_PATH);
            SetDlgItemText(hdlg, IDC_MESSAGE, szLink);
        } else {
            LoadString(g_hInstance, IDS_SP2_SDB, szLink, MAX_PATH);
            SetDlgItemText(hdlg, IDC_MESSAGE, szLink);
        }

        LoadString(g_hInstance, IDS_MSG_LINK, szLink, MAX_PATH);
        SetDlgItemText(hdlg, IDC_MSG_LINK, szLink);
        
        break;
    }

    case WM_NOTIFY:
        if (wParam == IDC_MSG_LINK) {
            
            NMHDR* pHdr = (NMHDR*)lParam;

            if (pHdr->code == NM_CLICK || pHdr->code == NM_RETURN) {
                
                SHELLEXECUTEINFO sei = { 0 };
                
                sei.cbSize = sizeof(SHELLEXECUTEINFO);
                sei.fMask  = SEE_MASK_DOENVSUBST;
                sei.hwnd   = hdlg;
                sei.nShow  = SW_SHOWNORMAL;
                sei.lpFile = g_szW2KUrl;

                ShellExecuteEx(&sei);
                break;
            }
        }
        break;
    
    case WM_COMMAND:
        switch (wCode) {
            
        case IDCANCEL:
            EndDialog(hdlg, TRUE);
            break;
        
        default:
            return FALSE;
        }
        break;

    default:
        return FALSE;
    }

    return TRUE;
}

INT_PTR CALLBACK
LayersTabDlgProc(
    HWND   hdlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
/*++
    LayersTabDlgProc

    Description:    Handle messages for the layers tab.

--*/
{
    int wCode = LOWORD(wParam);
    int wNotifyCode = HIWORD(wParam);

    switch (uMsg) {
    
    case WM_INITDIALOG:
        HandleLayersDialogInit(hdlg);
        break;
    
    case WM_COMMAND:

        if (wNotifyCode == LBN_SELCHANGE && wCode == IDC_LAYERS) {
            LayerChanged(hdlg);
            break;
        }

        switch (wCode) {
            
        case IDCANCEL:
            EndDialog(hdlg, TRUE);
            break;
        
        default:
            return FALSE;
        }
        break;

    default:
        return FALSE;
    }

    return TRUE;
}

INT_PTR CALLBACK
FixesTabDlgProc(
    HWND   hdlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
/*++
    LayersDlgProc

    Description:    Handle messages for the fixes tab.

--*/
{
    int wCode = LOWORD(wParam);
    int wNotifyCode = HIWORD(wParam);

    switch (uMsg) {
    
    case WM_INITDIALOG:
        if (!HandleFixesDialogInit(hdlg)) {
            EndDialog(g_hDlg, 0);
        }
        break;

    case WM_NOTIFY:
        if (wParam == IDC_SHIMS) {
            HandleShimListNotification(hdlg, lParam);
        }
        break;

    case WM_TIMER:
        if (wParam == ID_COUNT_SHIMS) {
            KillTimer(hdlg, ID_COUNT_SHIMS);
            CountShims(TRUE);
        }
        break;
    
    case WM_COMMAND:
        switch (wCode) {
            
        case IDCANCEL:
            EndDialog(hdlg, TRUE);
            break;

        case IDC_CLEAR_SHIMS:
            DeselectAllShims(hdlg);
            break;
        
        default:
            return FALSE;
        }
        break;

    default:
        return FALSE;
    }

    return TRUE;
}

void
HandleModuleListNotification(
    HWND   hdlg,
    LPARAM lParam
    )
/*++
    HandleModuleListNotification

    Description:    Handle all the notifications we care about for the
                    shim list.
--*/
{
    LPNMHDR pnm = (LPNMHDR)lParam;

    switch (pnm->code) {

    case NM_CLICK:
    {
        LVHITTESTINFO lvhti;
        LVITEM        lvi;
        
        GetCursorPos(&lvhti.pt);
        ScreenToClient(g_hwndShimList, &lvhti.pt);

        ListView_HitTest(g_hwndShimList, &lvhti);

        //
        // If the user clicked on a list view item,
        // enable the Remove button.
        //
        if (lvhti.flags & LVHT_ONITEMLABEL) {
            EnableWindow(GetDlgItem(hdlg, IDC_REMOVE), TRUE);
        }

        break;
    }
    default:
        break;
    }
}

void
HandleShimListNotification(
    HWND   hdlg,
    LPARAM lParam
    )
/*++
    HandleShimListNotification

    Description:    Handle all the notifications we care about for the
                    shim list.
--*/
{
    LPNMHDR pnm = (LPNMHDR)lParam;

    switch (pnm->code) {

    case NM_CLICK:
    {
        LVHITTESTINFO lvhti;
        LVITEM        lvi;
        
        GetCursorPos(&lvhti.pt);
        ScreenToClient(g_hwndShimList, &lvhti.pt);

        ListView_HitTest(g_hwndShimList, &lvhti);

        //
        // If the check box state has changed,
        // toggle the selection. Either way,
        // maintain selection as we go.
        //
        if (lvhti.flags & LVHT_ONITEMSTATEICON) {
            ShimListToggleSelection(lvhti.iItem, uReverse);
        }
        
        ListView_SetItemState(g_hwndShimList,
                              lvhti.iItem,
                              LVIS_FOCUSED | LVIS_SELECTED,
                              0x000F);

        SetTimer(hdlg, ID_COUNT_SHIMS, 100, NULL);
        break;
    }
    
    case NM_DBLCLK:
    {
        LVITEM  lvi;
        TCHAR   szShimName[MAX_PATH];
        int     nItem;
        PFIX    pFix;

        nItem = ListView_GetSelectionMark(g_hwndShimList);

        if (-1 == nItem) {
            break;
        }

        lvi.mask  = LVIF_PARAM;
        lvi.iItem = nItem;

        ListView_GetItem(g_hwndShimList, &lvi);

        pFix = (PFIX)lvi.lParam;

        // If this is a shim, display the options dialog.
        if (!pFix->bFlag) {

            if (DialogBoxParam(g_hInstance,
                               MAKEINTRESOURCE(IDD_OPTIONS),
                               hdlg,
                               OptionsDlgProc,
                               (LPARAM)pFix)) {
    
                if (NULL != pFix->pszCmdLine) {
                    ListView_SetItemText(g_hwndShimList, nItem, 1, _T("X"));
                } else {
                    ListView_SetItemText(g_hwndShimList, nItem, 1, _T(""));
                }

                if (NULL != pFix->pModule) {
                    ListView_SetItemText(g_hwndShimList, nItem, 2, _T("X"));
                } else {
                    ListView_SetItemText(g_hwndShimList, nItem, 2, _T(""));
                }
            }
        } else {
            MessageBeep(MB_ICONEXCLAMATION);
        }
        break;
    }
    
    case LVN_ITEMCHANGED:
    {
        LPNMLISTVIEW lpnmlv;
        PFIX         pFix;

        lpnmlv = (LPNMLISTVIEW)lParam;
        pFix = (PFIX)lpnmlv->lParam;

        //
        // Only change the text if our selection has changed.
        // If we don't do this, the text goes bye-bye when
        // the user clicks the Clear button.
        //
        if ((lpnmlv->uChanged & LVIF_STATE) &&
            (lpnmlv->uNewState & LVIS_SELECTED)) {
            SetDlgItemText(hdlg, IDC_SHIM_DESCRIPTION, pFix->pszDesc);
            ListView_SetSelectionMark(g_hwndShimList, lpnmlv->iItem);
        }
        break;
    }
    default:
        break;
    }
}

void
HandleAttributeTreeNotification(
    HWND   hdlg,
    LPARAM lParam
    )
/*++
    HandleAttributeTreeNotification

    Description:    Handle all the notifications we care about for the
                    file attributes tree.
--*/
{
    LPNMHDR pnm = (LPNMHDR)lParam;

    switch (pnm->code) {

    case NM_CLICK:
    {
        TVHITTESTINFO HitTest;
        HTREEITEM     hParentItem;

        GetCursorPos(&HitTest.pt);
        ScreenToClient(g_hwndFilesTree, &HitTest.pt);

        TreeView_HitTest(g_hwndFilesTree, &HitTest);

        if (HitTest.flags & TVHT_ONITEMSTATEICON) {
            FileTreeToggleSelection(HitTest.hItem, uReverse);
        
        } else if (HitTest.flags & TVHT_ONITEMLABEL) {

            HWND        hwndButton;
            HTREEITEM   hItem, hRoot;
            
            hwndButton = GetDlgItem(hdlg, IDC_REMOVE_MATCHING);

            hItem = TreeView_GetParent(g_hwndFilesTree, HitTest.hItem);

            hRoot = TreeView_GetRoot(g_hwndFilesTree);

            //
            // If the selected item has no parent and it's not
            // the root, enable the remove matching button.
            //
            if ((NULL == hItem) && (hRoot != HitTest.hItem)) {
                EnableWindow(hwndButton, TRUE);
            } else {
                EnableWindow(hwndButton, FALSE);
            }
        }
        break;
    }

    case NM_RCLICK:
    {
        TVHITTESTINFO HitTest;
        POINT         pt;

        GetCursorPos(&HitTest.pt);
        
        pt.x = HitTest.pt.x;
        pt.y = HitTest.pt.y;

        ScreenToClient(g_hwndFilesTree, &HitTest.pt);

        TreeView_HitTest(g_hwndFilesTree, &HitTest);

        if (HitTest.flags & TVHT_ONITEMLABEL) 
        {
            HTREEITEM hItem, hParentItem;

            TreeView_SelectItem(g_hwndFilesTree, HitTest.hItem);

            //
            // If the selected item has no parent, we assume that a
            // matching file was right-clicked.
            //
            hParentItem = TreeView_GetParent(g_hwndFilesTree, HitTest.hItem);

            if (NULL == hParentItem) {
                DisplayAttrContextMenu(&pt);
            }
        }
        break;
    }
    case TVN_KEYDOWN:
    {
        LPNMTVKEYDOWN lpKeyDown = (LPNMTVKEYDOWN)lParam;
        HTREEITEM     hItem;

        if (lpKeyDown->wVKey == VK_SPACE) {

            hItem = TreeView_GetSelection(g_hwndFilesTree);

            if (hItem != NULL) {
                FileTreeToggleSelection(hItem, uReverse);
            }
        } else if (lpKeyDown->wVKey == VK_DELETE) {

            HTREEITEM hParentItem;

            hItem = TreeView_GetSelection(g_hwndFilesTree);
            
            hParentItem = TreeView_GetParent(g_hwndFilesTree, hItem);

            if (hParentItem == NULL) {
                if (TreeView_GetPrevSibling(g_hwndFilesTree, hItem) != NULL) {
                    TreeView_DeleteItem(g_hwndFilesTree, hItem);
                }
            }
        }
        break;
    }
    default:
        break;
    }
}

INT_PTR CALLBACK
QFixAppDlgProc(
    HWND   hdlg,
    UINT   uMsg,
    WPARAM wParam,
    LPARAM lParam
    )
/*++
    QFixAppDlgProc

    Description:    The dialog proc of QFixApp.

--*/
{
    int wCode = LOWORD(wParam);
    int wNotifyCode = HIWORD(wParam);

    switch (uMsg) {
    case WM_INITDIALOG:
        if (!InitMainDialog(hdlg)) {
            EndDialog(hdlg, TRUE);
        }
        break;

    case WM_NOTIFY:
        if (wParam == IDC_SHIMS) {
            HandleShimListNotification(hdlg, lParam);
        } else if (wParam == IDC_ATTRIBUTES) {
            HandleAttributeTreeNotification(hdlg, lParam);
        } else if (wParam == IDC_TAB_FIXES) {
            HandleTabNotification(hdlg, lParam);
        } else if (wParam == IDC_DOWNLOAD_WU) {
            
            NMHDR* pHdr = (NMHDR*)lParam;

            if (pHdr->code == NM_CLICK || pHdr->code == NM_RETURN) {
                
                SHELLEXECUTEINFO sei = { 0 };
                
                sei.cbSize = sizeof(SHELLEXECUTEINFO);
                sei.fMask  = SEE_MASK_DOENVSUBST;
                sei.hwnd   = hdlg;
                sei.nShow  = SW_SHOWNORMAL;
                sei.lpFile = g_fW2K ? g_szW2KUrl : g_szXPUrl;

                ShellExecuteEx(&sei);
                break;
            }
        }
        break;

    case WM_DESTROY:
    {
        DLGHDR* pHdr;

        //
        // Destory the dialogs and remove any misc files.
        //
        pHdr = (DLGHDR*)GetWindowLongPtr(hdlg, DWLP_USER);

        DestroyWindow(pHdr->hDisplay[0]);
        DestroyWindow(pHdr->hDisplay[1]);

        CleanupSupportForApp(g_szShortName);

        break;
    }

    case WM_COMMAND:
        
        if (wNotifyCode == LBN_SELCHANGE && wCode == IDC_LAYERS) {
            LayerChanged(hdlg);
            break;
        }
        
        switch (wCode) {

        case IDC_RUN:
            RunTheApp(hdlg);
            break;

        case IDC_BROWSE:
            BrowseForApp(hdlg);
            break;
        
        case IDC_DETAILS:
            ExpandCollapseDialog(hdlg, !g_bSimpleEdition);
            break;

        case IDC_CREATEFILE:
            CreateSupportForApp(hdlg);
            break;
        
        case IDC_SHOWXML:
            ShowXML(hdlg);
            break;

        case IDC_ADD_MATCHING:
            PromptAddMatchingFile(hdlg);
            break;

        case IDC_VIEW_LOG:
            ShowShimLog();
            break;

        case IDCANCEL:
            EndDialog(hdlg, TRUE);
            break;

        case IDM_SELECT_ALL:
            SelectAttrsInTree(TRUE);
            break;

        case IDM_CLEAR_ALL:
            SelectAttrsInTree(FALSE);
            break;

        case IDC_REMOVE_MATCHING:
        {
            HTREEITEM   hParentItem, hItem;
            TCHAR       szError[MAX_PATH];

            hItem = TreeView_GetSelection(g_hwndFilesTree);

            if (NULL == hItem) {
                LoadString(g_hInstance, IDS_NO_SELECTION, szError, MAX_PATH);
                MessageBox(hdlg, szError, g_szAppTitle, MB_ICONEXCLAMATION | MB_OK);
                return TRUE;
            }
            
            hParentItem = TreeView_GetParent(g_hwndFilesTree, hItem);

            if (hParentItem == NULL) {
                if (TreeView_GetPrevSibling(g_hwndFilesTree, hItem) != NULL) {
                    TreeView_DeleteItem(g_hwndFilesTree, hItem);
                    EnableWindow(GetDlgItem(hdlg, IDC_REMOVE_MATCHING), FALSE);
                }
            }
            break;
        }

        default:
            return FALSE;
        }
        break;

    default:
        return FALSE;
    }

    return TRUE;
}


int WINAPI
wWinMain(
    HINSTANCE hInst,
    HINSTANCE hInstPrev,
    LPTSTR    lpszCmd,
    int       swShow
    )
/*++
    WinMain

    Description:    Application entry point.

--*/
{
    BOOL                    fSP2 = FALSE;
    TCHAR                   szError[MAX_PATH];
    OSVERSIONINFO           osvi;
    INITCOMMONCONTROLSEX    icex;
    
    icex.dwSize    = sizeof(INITCOMMONCONTROLSEX);
    icex.dwICC     = ICC_LISTVIEW_CLASSES | ICC_TREEVIEW_CLASSES | ICC_TAB_CLASSES;

    if (!InitCommonControlsEx(&icex)) {
        InitCommonControls();
    }

    LoadString(g_hInstance, IDS_APP_TITLE, g_szAppTitle, 64);

    LinkWindow_RegisterClass();

    g_hInstance = hInst;

    osvi.dwOSVersionInfoSize = sizeof(osvi);

    GetVersionEx(&osvi);

    //
    // See if they're an administrator - bail if not.
    //
    if (!(IsUserAnAdministrator())) {
        LoadString(g_hInstance, IDS_NOT_ADMIN, szError, MAX_PATH);
        MessageBox(NULL, szError, g_szAppTitle, MB_ICONERROR | MB_OK);
        return 0;
    }

    //
    // See if we're running on Windows 2000.
    //
    if ((osvi.dwMajorVersion == 5 && osvi.dwMinorVersion == 0)) {
        g_fW2K = TRUE;
    }

    // See if we're running on SP2
    if (!(_tcscmp(osvi.szCSDVersion, _T("Service Pack 2")))) {
        fSP2 = TRUE;
    }

    //
    // Attempt to locate the SDB in the AppPatch directory.
    //
    if (!CheckForSDB()) {
        if (g_fW2K) {
            DialogBoxParam(hInst,
                           MAKEINTRESOURCE(IDD_MSGBOX_SDB),
                           GetDesktopWindow(),
                           MsgBoxDlgProc,
                           (LPARAM)1);
            return 0;
        } else {
            LoadString(g_hInstance, IDS_XP_NO_SDB, szError, MAX_PATH);
            MessageBox(GetDesktopWindow(), szError, g_szAppTitle, MB_OK | MB_ICONEXCLAMATION);
            return 0;
        }
    }

    //
    // If this is SP2, and the SDB is older, bail out.
    //
    if (fSP2) {
        if (IsSDBFromSP2()) {
            DialogBoxParam(hInst,
                           MAKEINTRESOURCE(IDD_MSGBOX_SP2),
                           GetDesktopWindow(),
                           MsgBoxDlgProc,
                           (LPARAM)0);
            return 0;
        }
    }

    LogMsg(_T("[WinMain] Command line \"%s\"\n"), lpszCmd);

    //
    // Check for command line options.
    //
    if (*lpszCmd == _T('a') || *lpszCmd == _T('A')) {
        g_bAllShims = TRUE;
    }

    DialogBox(hInst,
              MAKEINTRESOURCE(IDD_DIALOG),
              GetDesktopWindow(),
              QFixAppDlgProc);

    return 1;
}