/*==========================================================================*/
//
//  mmcpl.c
//
//  Copyright (C) 1993-1994 Microsoft Corporation.  All Rights Reserved.
//
//    06/94    -Created- VijR
//
/*==========================================================================*/

#pragma warning( disable: 4103)
#include "mmcpl.h"
#include <cpl.h>
#define NOSTATUSBAR
#include <commctrl.h>
#include <prsht.h>
#include <regstr.h>
#include <infstr.h>
#include <devguid.h>

#include "draw.h"
#include "utils.h"
#include "drivers.h"
#include "sulib.h"
#include <tchar.h>
#include <hwtab.h>
#include "debug.h"

#ifndef cchRESOURCE
    #define cchRESOURCE 256
#endif

/*
 ***************************************************************
 * Globals
 ***************************************************************
 */
HINSTANCE   ghInstance  = NULL;
BOOL        gfNukeExt   = -1;
HWND        ghwndMsgBox = NULL;
HWND        ghwndAdvProp = NULL;
BOOL        gfVoiceTab  = FALSE;

#ifdef FIX_BUG_15451
static TCHAR cszFORKLINE[] = TEXT("RUNDLL32.EXE MMSYS.CPL,ShowDriverSettingsAfterFork %s");
#endif // FIX_BUG_15451

SZCODE cszAUDIO[] = AUDIO;
SZCODE cszVIDEO[] = VIDEO;
SZCODE cszCDAUDIO[] = CDAUDIO;
SZCODE cszMIDI[] = MIDI;
SZCODE cszVOICE[]    = VOICE;
SZCODE cszVOLUME[]    = VOLUME;

/*
 ***************************************************************
 *  Typedefs
 ***************************************************************
 */

typedef struct _ExtPropSheetCBParam //Callback Parameter
{
    HTREEITEM hti;
    LPPROPSHEETHEADER    ppsh;
    LPARAM lParam1;    //PIRESOURCE/PINSTRUMENT etc. depending on node. (OR) Simple propsheet class
    LPARAM lParam2; //hwndTree (OR) Simple propsheet name
} EXTPROPSHEETCBPARAM, *PEXTPROPSHEETCBPARAM;

typedef struct _MBInfo
{
    LPTSTR szTitle;
    LPTSTR szMsg;
    UINT  uStyle;
} MBINFO, *PMBINFO;


/*
 ***************************************************************
 * Defines
 ***************************************************************
 */

#define    MAXPAGES    8    // MAX number of sheets allowed
#define    MAXMODULES    32    // MAX number of external modules allowed
#define    MAXCLASSSIZE    64

#define cComma    TEXT(',')
#define PROPTABSIZE 13

#define GetString(_str,_id,_hi)  LoadString (_hi, _id, _str, sizeof(_str)/sizeof(TCHAR))

/*
 ***************************************************************
 * File Globals
 ***************************************************************
 */
static SZCODE    aszSimpleProperties[] = REGSTR_PATH_MEDIARESOURCES TEXT("\\MediaExtensions\\shellx\\SimpleProperties\\");
static SZCODE    aszShellName[]    = TEXT("ShellName");

static UINT     g_cRefCnt;            // keeps track of the ref count
static int      g_cProcesses        = 0;
static int      g_nStartPage        = 0;

/*
 ***************************************************************
 * Prototypes
 ***************************************************************
 */
INT_PTR CALLBACK AudioDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK VideoDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK CDDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK ACMDlg(HWND hwnd,UINT uMsg,WPARAM wParam,LPARAM lParam);
INT_PTR CALLBACK SoundDlg(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK VolumeDlg(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK AddDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK AdvDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
INT_PTR CALLBACK HardwareDlgProc(HWND hdlg, UINT uMsg, WPARAM wp, LPARAM lp);
INT_PTR CALLBACK VoiceDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);
//INT_PTR CALLBACK EffectDlg(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam);


//
// This is the dialog procedure for the "Hardware" page.
//


INT_PTR CALLBACK HardwareDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    static HWND s_hwndHW = NULL;

    switch (uMessage)
    {
        case WM_NOTIFY:
        {
            NMHDR * pnmhdr = (NMHDR *) lParam;
            int code = pnmhdr->code;

            switch (code)
            {
                case HWN_FILTERITEM:
                {
                    NMHWTAB *pnmht = (NMHWTAB *) lParam;
                    BOOL fFilter = FALSE;

                    if (!pnmht->fHidden)    // Let's not bother looking at devices already hidden
                    {
                        fFilter = FALSE;
                    }

                    return(TRUE);
                }
                break;

                case HWN_SELECTIONCHANGED:
                {
                    NMHWTAB *pnmht = (NMHWTAB *) lParam;

                    if (pnmht)
                    {
                        if (pnmht->pdinf)
                        {
                            if (IsEqualGUID(&(pnmht->pdinf->ClassGuid),&GUID_DEVCLASS_CDROM))
                            {
                                SetWindowText(s_hwndHW, TEXT("hh.exe ms-its:tshoot.chm::/hdw_drives.htm"));
                            }
                            else
                            {
                                SetWindowText(s_hwndHW, TEXT("hh.exe ms-its:tshoot.chm::/tssound.htm"));
                            }
                        }
                    }
                }
                break;
            }
        }
        break;

        case WM_INITDIALOG:
        {
            GUID guidClass[2];

            guidClass[0] = GUID_DEVCLASS_CDROM;
            guidClass[1] = GUID_DEVCLASS_MEDIA;

            s_hwndHW = DeviceCreateHardwarePageEx(hDlg, (const GUID *) &guidClass, 2, HWTAB_LARGELIST );

            if (s_hwndHW)
            {
                SetWindowText(s_hwndHW, TEXT("hh.exe ms-its:tshoot.chm::/tssound.htm"));
            }
            else
            {
                DestroyWindow(hDlg); // catastrophic failure
            }
        }
        return FALSE;
    }

    return FALSE;
}



INT_PTR CALLBACK CD_HardwareDlgProc(HWND hDlg, UINT uMessage, WPARAM wParam, LPARAM lParam)
{
    switch (uMessage)
    {
        case WM_INITDIALOG:
        {
            HWND hwndHW;

            hwndHW = DeviceCreateHardwarePageEx(hDlg, &GUID_DEVCLASS_CDROM, 1, HWTAB_SMALLLIST);

            if (hwndHW)
            {
                SetWindowText(hwndHW, TEXT("hh.exe ms-its:tshoot.chm::/hdw_multi.htm"));
            }
            else
            {
                DestroyWindow(hDlg); // catastrophic failure
            }
        }
        return FALSE;
    }

    return FALSE;
}



/*
 ***************************************************************
 ***************************************************************
 */

INT_PTR FAR PASCAL mmse_MessageBoxProc(HWND hDlg, UINT wMsg, WPARAM wParam, LPARAM lParam)
{
    switch (wMsg)
    {
    case WM_INITDIALOG:
        {
            PMBINFO pmbInfo = (PMBINFO)lParam;
            UINT uStyle = pmbInfo->uStyle;

            SetWindowText(hDlg, pmbInfo->szTitle);
            SetWindowText(GetDlgItem(hDlg, MMSE_TEXT), pmbInfo->szMsg);
            if (IsFlagClear(uStyle, MMSE_OK))
                DestroyWindow(GetDlgItem(hDlg, MMSE_OK));
            if (IsFlagClear(uStyle, MMSE_YES))
                DestroyWindow(GetDlgItem(hDlg, MMSE_YES));
            if (IsFlagClear(uStyle, MMSE_NO))
                DestroyWindow(GetDlgItem(hDlg, MMSE_NO));
            ghwndMsgBox = hDlg;
            break;
        }
    case WM_DESTROY:
        ghwndMsgBox = NULL;
        break;
    case WM_COMMAND:
        {
            switch (GET_WM_COMMAND_ID(wParam, lParam))
            {
            case MMSE_YES:
                EndDialog(hDlg, MMSE_YES);
                break;
            case MMSE_NO:
                EndDialog(hDlg, MMSE_NO);
                break;
            case MMSE_OK:
                EndDialog(hDlg, MMSE_OK);
                break;
            }
            break;
        }
    default:
        return FALSE;
    }
    return TRUE;
}

INT_PTR mmse_MessageBox(HWND hwndP,  LPTSTR szMsg, LPTSTR szTitle, UINT uStyle)
{
    MBINFO mbInfo;

    mbInfo.szMsg = szMsg;
    mbInfo.szTitle = szTitle;
    mbInfo.uStyle = uStyle;

    return DialogBoxParam(ghInstance, MAKEINTRESOURCE(DLG_MESSAGE_BOX), hwndP, mmse_MessageBoxProc, (LPARAM)&mbInfo);
}

/*==========================================================================*/
int FAR PASCAL lstrncmpi(
                        LPCTSTR    lszKey,
                        LPCTSTR    lszClass,
                        int    iSize)
{
    TCHAR    aszKey[64];

    lstrcpyn(aszKey, lszKey, iSize);
    return lstrcmpi(aszKey, lszClass);
}

int StrByteLen(LPTSTR sz)
{
    LPTSTR psz;

    if (!sz)
        return 0;
    for (psz = sz; *psz; psz = CharNext(psz))
        ;
    return (int)(psz - sz);
}

static void NukeExt(LPTSTR sz)
{
    int len;

    len = StrByteLen(sz);

    if (len > 4 && sz[len-4] == TEXT('.'))
        sz[len-4] = 0;
}

static LPTSTR NukePath(LPTSTR sz)
{
    LPTSTR pTmp, pSlash;

    for (pSlash = pTmp = sz; *pTmp; pTmp = CharNext(pTmp))
    {
        if (*pTmp == TEXT('\\'))
            pSlash = pTmp;
    }
    return (pSlash == sz ? pSlash : pSlash+1);
}

void    CheckNukeExtOption(LPTSTR sz)
{
    SHFILEINFO sfi;

    SHGetFileInfo(sz, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME);
    if (lstrcmpi((LPTSTR)(sfi.szDisplayName+lstrlen(sfi.szDisplayName)-4), cszWavExt))
        gfNukeExt = TRUE;
    else
        gfNukeExt = FALSE;
}

LPTSTR PASCAL NiceName(LPTSTR sz, BOOL fNukePath)
{
    SHFILEINFO sfi;

    if (gfNukeExt == -1)
        CheckNukeExtOption(sz);

    if (!SHGetFileInfo(sz, 0, &sfi, sizeof(sfi), SHGFI_DISPLAYNAME))
        return sz;

    if (fNukePath)
    {
        lstrcpy(sz, sfi.szDisplayName);
    }
    else
    {
        LPTSTR lpszFileName;

        lpszFileName = NukePath(sz);
        lstrcpy(lpszFileName, sfi.szDisplayName);
        if (lpszFileName != sz)
            CharUpperBuff(sz, 1);
    }
    return sz;
}



/*
 ***************************************************************
 * ErrorBox
 *
 * Description:
 *        Brings up error Dialog displaying error
 *
 * Parameters:
 *        HWND    hDlg  - Window handle
 *        int        iResource    - id of the resource to be loaded
 *        LPTSTR    lpszDesc - The string to be inserted in the resource string
 *
 * Returns:            BOOL
 *
 ***************************************************************
 */
BOOL PASCAL ErrorBox(HWND hDlg, int iResource, LPTSTR lpszDesc)
{
    TCHAR szBuf[MAXMSGLEN];
    TCHAR szTitle[MAXSTR];
    TCHAR szResource[MAXMSGLEN];

    LoadString(ghInstance, iResource, szResource, MAXSTR);
    LoadString(ghInstance, IDS_ERROR, szTitle, MAXSTR);
    wsprintf(szBuf, szResource, lpszDesc);
    MessageBox(hDlg, szBuf, szTitle, MB_APPLMODAL | MB_OK |MB_ICONSTOP);
    return TRUE;
}

int PASCAL DisplayMessage(HWND hDlg, int iResTitle, int iResMsg, UINT uStyle)
{
    TCHAR szBuf[MAXMSGLEN];
    TCHAR szTitle[MAXSTR];
    UINT uAddStyle = MB_APPLMODAL;

    if (!LoadString(ghInstance, iResTitle, szTitle, MAXSTR))
        return FALSE;
    if (!LoadString(ghInstance, iResMsg, szBuf, MAXSTR))
        return FALSE;
    if (uStyle & MB_OK)
        uAddStyle |= MB_ICONASTERISK;
    else
        uAddStyle |= MB_ICONQUESTION;
    return MessageBox(hDlg, szBuf, szTitle,  uStyle | uAddStyle);
}


//Adds spaces around Tab Names to make them all approx. same size.
STATIC void PadWithSpaces(LPTSTR szName, LPTSTR szPaddedName)
{
    static SZCODE cszFmt[] = TEXT("%s%s%s");
    TCHAR szPad[8];
    int i;

    i = PROPTABSIZE - lstrlen(szName);

    i = (i <= 0) ? 0 : i/2;
    for (szPad[i] = TEXT('\0');i; i--)
        szPad[i-1] =  TEXT(' ');
    wsprintf(szPaddedName, cszFmt, szPad, szName, szPad);
}

/*==========================================================================*/
UINT CALLBACK  CallbackPage(
                           HWND        hwnd,
                           UINT        uMsg,
                           LPPROPSHEETPAGE    ppsp)
{
    if (uMsg == PSPCB_RELEASE)
    {
        DPF_T("* RelasePage %s *", (LPTSTR)ppsp->pszTitle);
    }
    return 1;
}

/*==========================================================================*/
static BOOL PASCAL NEAR AddPage(
                               LPPROPSHEETHEADER    ppsh,
                               LPCTSTR            pszTitle,
                               DLGPROC            pfnDialog,
                               UINT            idTemplate,
                               LPARAM            lParam)
{
    if (ppsh->nPages < MAXPAGES)
    {

        if (pfnDialog)
        {
            PROPSHEETPAGE    psp;
            psp.dwSize = sizeof(PROPSHEETPAGE);
            psp.dwFlags = PSP_DEFAULT | PSP_USETITLE | PSP_USECALLBACK;
            psp.hInstance = ghInstance;
            psp.pszTemplate = MAKEINTRESOURCE(idTemplate);
            psp.pszIcon = NULL;
            psp.pszTitle = pszTitle;
            psp.pfnDlgProc = pfnDialog;
            psp.lParam = (LPARAM)lParam;
            psp.pfnCallback = CallbackPage;
            psp.pcRefParent = NULL;
            if (ppsh->phpage[ppsh->nPages] = CreatePropertySheetPage(&psp))
            {
                ppsh->nPages++;
                return TRUE;
            }
        }
    }
    return FALSE;
}

/*==========================================================================*/
BOOL CALLBACK MMExtPropSheetCallback(DWORD dwFunc, DWORD_PTR dwParam1, DWORD_PTR dwParam2, DWORD_PTR dwInstance)
{
    PEXTPROPSHEETCBPARAM pcbp = (PEXTPROPSHEETCBPARAM)dwInstance;

    if (!pcbp && dwFunc != MM_EPS_BLIND_TREECHANGE)
        return FALSE;
    switch (dwFunc)
    {
    case MM_EPS_GETNODEDESC:
        {
            if (!dwParam1)
                return FALSE;
            if (pcbp->hti == NULL)
                lstrcpy((LPTSTR)dwParam1, (LPTSTR)pcbp->lParam2);
            else
            {
                GetTreeItemNodeDesc ((LPTSTR)dwParam1,
                                     (PIRESOURCE)pcbp->lParam1);
            }
            break;
        }
    case MM_EPS_GETNODEID:
        {
            if (!dwParam1)
                return FALSE;
            if (pcbp->hti == NULL)
                lstrcpy((LPTSTR)dwParam1, (LPTSTR)pcbp->lParam2);
            else
            {
                GetTreeItemNodeID ((LPTSTR)dwParam1,
                                   (PIRESOURCE)pcbp->lParam1);
            }
            break;
        }
    case MM_EPS_ADDSHEET:
        {
            HPROPSHEETPAGE    hpsp = (HPROPSHEETPAGE)dwParam1;

            if (hpsp && (pcbp->ppsh->nPages < MAXPAGES))
            {
                pcbp->ppsh->phpage[pcbp->ppsh->nPages++] = hpsp;
                return TRUE;
            }
            return FALSE;
        }
    case MM_EPS_TREECHANGE:
        {
            RefreshAdvDlgTree ();
            break;
        }
    case MM_EPS_BLIND_TREECHANGE:
        {
            RefreshAdvDlgTree ();
            break;
        }
    default:
        return FALSE;
    }
    return TRUE;
}

INT_PTR CALLBACK SpeechDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

/*==========================================================================*/
static BOOL PASCAL NEAR AddSpeechPage(LPPROPSHEETHEADER    ppsh)
{
    TCHAR    aszTitleRes[128];
    TCHAR     szTmp[32];

    LoadString(ghInstance, IDS_SPEECH_NAME, aszTitleRes, sizeof(aszTitleRes)/sizeof(TCHAR));
    PadWithSpaces((LPTSTR)aszTitleRes, (LPTSTR)szTmp);
    return AddPage(ppsh, szTmp, SpeechDlgProc, IDD_SPEECH, (LPARAM)NULL);
}

/*==========================================================================*/
static BOOL PASCAL NEAR AddAdvancedPage(
                                       LPPROPSHEETHEADER    ppsh)
{
    TCHAR    aszTitleRes[128];
    TCHAR     szTmp[32];

    LoadString(ghInstance, IDS_ADVANCED, aszTitleRes, sizeof(aszTitleRes)/sizeof(TCHAR));
    PadWithSpaces((LPTSTR)aszTitleRes, (LPTSTR)szTmp);
    return AddPage(ppsh, szTmp, AdvDlg, ADVDLG, (LPARAM)NULL);
}

/*==========================================================================*/
static BOOL PASCAL NEAR AddHardwarePage(
                                       LPPROPSHEETHEADER    ppsh)
{
    TCHAR    aszTitleRes[128];
    TCHAR     szTmp[32];

    // Don't add a hardware tab if the admin restricted it
    if (SHRestricted(REST_NOHARDWARETAB))
        return FALSE;

    LoadString(ghInstance, IDS_HARDWARE, aszTitleRes, sizeof(aszTitleRes)/sizeof(TCHAR));
    PadWithSpaces((LPTSTR)aszTitleRes, (LPTSTR)szTmp);
    return AddPage(ppsh, szTmp, HardwareDlgProc, HWDLG, (LPARAM)NULL);
}

/*==========================================================================*/
static BOOL PASCAL NEAR AddSchemesPage(
                                      LPPROPSHEETHEADER    ppsh)
{
    TCHAR    aszTitleRes[128];

    LoadString(ghInstance, IDS_EVENTSNAME, aszTitleRes, sizeof(aszTitleRes)/sizeof(TCHAR));
    return AddPage(ppsh, aszTitleRes, SoundDlg, SOUNDDIALOG, (LPARAM)NULL);
}

/*==========================================================================*/

static void PASCAL NEAR AddInternalPages (LPPROPSHEETHEADER ppsh)
{
    static EXTPROPSHEETCBPARAM cbp;
    TCHAR  szText[ cchRESOURCE ];
    TCHAR  szPadded[ cchRESOURCE ];


    // Add the Volume page
    //
    GetString (szText, IDS_VOLUMENAME, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, VolumeDlg, IDD_VOLUME, (LPARAM)NULL);

    // Add the Sound Scheme page
    //
    GetString (szText, IDS_EVENTSNAME, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, SoundDlg, SOUNDDIALOG, (LPARAM)NULL);

    // Add the Audio page
    //
    GetString (szText, IDS_AUDIO_TAB, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, AudioDlg, AUDIODLG, (LPARAM)NULL);

    // Add the Voice page
    //
    GetString (szText, IDS_VOICE, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, VoiceDlg, VOICEDLG, (LPARAM)NULL);

    // Add the Video page
    //
 /*   GetString (szText, IDS_VIDEO_TAB, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, VideoDlg, VIDEODLG, (LPARAM)NULL); */

    // Add the MIDI page
    //
 /*   GetString (szText, IDS_MIDI_TAB, ghInstance);
    PadWithSpaces (szText, szPadded);
    cbp.ppsh = ppsh;
    cbp.hti = NULL;
    cbp.lParam1 = (LPARAM)cszMIDI;
    cbp.lParam2 = (LPARAM)szPadded;
    AddSimpleMidiPages ((LPVOID)szPadded, MMExtPropSheetCallback, (LPARAM)&cbp);
*/

    // Add the CD Audio page
    //
 /*   GetString (szText, IDS_CDAUDIO_TAB, ghInstance);
    PadWithSpaces (szText, szPadded);
    AddPage (ppsh, szPadded, CDDlg, CDDLG, (LPARAM)NULL);
*/
}


static void InitPSH(LPPROPSHEETHEADER ppsh, HWND hwndParent, LPTSTR pszCaption, HPROPSHEETPAGE    FAR * phpsp)
{
    ppsh->dwSize = sizeof(PROPSHEETHEADER);
    ppsh->dwFlags = PSH_PROPTITLE;
    ppsh->hwndParent = hwndParent;
    ppsh->hInstance = ghInstance;
    ppsh->pszCaption = pszCaption;
    ppsh->nPages = 0;
    ppsh->nStartPage = 0;
    ppsh->phpage = phpsp;
}


/*==========================================================================*/
#ifdef FIX_BUG_15451
static void PASCAL cplMMDoubleClick (HWND hCPlWnd, int nStartPage)
#else // FIX_BUG_15451
static void PASCAL cplMMDoubleClick (HWND hCPlWnd)
#endif // FIX_BUG_15451
{
    PROPSHEETHEADER   psh;
    HPROPSHEETPAGE    hpsp[MAXPAGES];
    TCHAR strOldDir[MAX_PATH], strSysDir[MAX_PATH];

    strOldDir[0] = TEXT('\0');
    strSysDir[0] = TEXT('\0');

    GetSystemDirectory(strSysDir, MAX_PATH);
    GetCurrentDirectory(MAX_PATH, strOldDir);
    SetCurrentDirectory(strSysDir);
    wsInfParseInit();

    InitCommonControls();
    OleInitialize(NULL);

    RegSndCntrlClass((LPCTSTR)DISPFRAMCLASS);
    InitPSH(&psh,hCPlWnd,(LPTSTR)MAKEINTRESOURCE(IDS_MMNAME),hpsp);

#ifdef FIX_BUG_15451
    psh.nStartPage = nStartPage;
#else // FIX_BUG_15451
    psh.nStartPage = g_nStartPage;
#endif // FIX_BUG_15451
    g_nStartPage = 0;
    AddInternalPages(&psh);
    //AddSpeechPage(&psh);
    //AddAdvancedPage(&psh);
    AddHardwarePage(&psh);
    PropertySheet(&psh);

    OleUninitialize();

    infClose(NULL);
    SetCurrentDirectory(strOldDir);
}

/*==========================================================================*/
static void PASCAL cplEventsDoubleClick (HWND hCPlWnd)
{
    PROPSHEETHEADER    psh;
    HPROPSHEETPAGE    hpsp[MAXPAGES];

    InitCommonControls();
    RegSndCntrlClass((LPCTSTR)DISPFRAMCLASS);
    InitPSH(&psh,hCPlWnd,(LPTSTR)MAKEINTRESOURCE(IDS_EVENTSNAME),hpsp);
    AddSchemesPage(&psh);
    PropertySheet(&psh);
}

#ifdef FIX_BUG_15451
/*==========================================================================*/
/*
 * ShowDriverSettings
 * ShowDriverSettingsAfterFork
 *
 * When the user selects DevicesTab.<anydevice>.Properties.Settings, a
 * DRV_CONFIGURE message is sent to the selected user-mode driver, to cause
 * it to display its configuration dialog.  The sound drivers shipped with
 * NT (SNDBLST,MVAUDIO,SNDSYS) exhibit a bug in this condition: when the
 * configuration dialog is complete (regardless of whether OK or CANCEL was
 * selected), these drivers attempt to unload-and-reload their kernel-mode
 * component in order to begin using the new (or restore the original)
 * driver settings.  The unload request fails, because both the Audio tab
 * and SNDVOL.EXE have open mixer handles and pending IRPs within the kernel
 * driver (the latter are used to provide notifications of volume changes).
 * Worse, when the unload fails, it leaves the driver useless: its state
 * remains STOP_PENDING, and it cannot be resurrected without logging off
 * and back on.
 *
 * These routines have been provided as a temporary workaround for bug 15451,
 * which describes the problem mentioned above.  The theory behind this
 * solution is two-fold:
 *   1- close SNDVOL.EXE as soon as a driver's configuration dialog is
 *      to be displayed, and restart it directly thereafter.  This prevents
 *      it from maintaining any open handles to and/or pending IRPs within the
 *      kernel driver.
 *   2- if the Audio tab has ever been displayed, it will have open mixers
 *      which must be closed.  Because a bug/design flaw within these sound
 *      drivers prevents the mixers from being closed without killing this
 *      process (the sound drivers each cache open mixer handles), the
 *      routine ShowDriverSettings forks a new MMSYS.CPL process, which is
 *      then used to display the driver's settings dialog.
 *
 * The flow of this solution follows:
 *
 * 1- MMSYS.CPL starts on Audio tab, setting fHaveStartedAudioDialog to TRUE.
 * 2- User selects Devices tab.
 * 3- User selects a device driver.
 * 4- User selects Properties+Settings; control reaches ShowDriverSettings().
 * 5- ShowDriverSettings() determines if there is a need to fork a new process:
 *    this will be the case if the Audio tab has been displayed, and the
 *    device for which it is to display settings contains mixers.  If either
 *    of these conditions is false, ShowDriverSettings displays the driver's
 *    settings dialog directly (via ConfigureDriver()).
 * 6- ShowDriverSettings() uses WinExec() to fork a new process, using
 *    the routine ShowDriverSettingsAfterFork() as an entry point.  If the
 *    exec fails, ShowDriverSettings() displays the driver's settings dialog
 *    directly (via ConfigureDriver()).
 * PROCESS 1:                           PROCESS 2:
 * 7- Enters WaitForNewCPLWindow(),     1- ShowDriverSettingsAfterFork() will
 *    which will wait up to 5 seconds      receive on its command-line the
 *    for the new MMSYS.CPL process        name of the driver for which
 *    to open a driver Properties          settings have been requested.  It
 *    dialog which matches its own:        opens the primary dialog, using the
 *    if it finds such a dialog,           Devices tab as the initial tab--
 *    WaitForNewCPLWindow() will post      so that the Advanced tab is never
 *    IDCANCEL messages to both the        displayed, and because the Devices
 *    current driver Properties dialog,    tab is the active tab on the other
 *    and to this process's main           process.
 *    dialog, terminating this process. 2- During WM_INITDIALOG of the Devices
 *                                         dialog, this process searches for
 *                                       the previous process' MMSYS.CPL dialog.
 *                                     If successful, it moves this MMSYS.CPL
 *                                   dialog directly behind the previous dialog.
 *                              3- During ID_INIT of the Devices dialog, this
 *                               process searches the TreeView for the driver
 *                             which was named on the comand-line: if found,
 *                           it highlights the TreeItem and simulates a press
 *                         of the Properties button
 *                    4- During WM_INITDIALOG of the device's Properties dialog,
 *                     this process searches for the previous process' device's
 *                   properties dialog.  If successful, it moves this dialog
 *                 directly behind its counterpart.
 *            5- During ID_INIT of the device's Properties dialog, this process
 *             simulates a press of the Settings button
 *        6- When the Settings button is pressed, this process recognizes that
 *         it has been forked and skips the call to ShowDriverSettings(),
 *       instead simply displaying the driver's settings dialog (via
 *     ConfigureDriver()).
 *
 * Let it be known that this is a hack, and should be removed post-beta.
 *
 */

extern BOOL fHaveStartedAudioDialog;    // in MSACMCPL.C

void ShowDriverSettings (HWND hDlg, LPTSTR pszName)
{
    if (fHaveStartedAudioDialog && fDeviceHasMixers (pszName))
    {
        TCHAR  szForkLine[ cchRESOURCE *2 ];

        STARTUPINFO si;
        PROCESS_INFORMATION pi;

        memset(&si, 0, sizeof(si));
        si.cb = sizeof(si);
        si.wShowWindow = SW_SHOW;
        si.dwFlags = STARTF_USESHOWWINDOW;

        wsprintf (szForkLine, cszFORKLINE, pszName);

        if (CreateProcess(NULL,szForkLine,NULL,NULL,FALSE,0,NULL,NULL,&si,&pi))
        {
            (void)WaitForNewCPLWindow (hDlg);
        }
        else
        {
            ConfigureDriver (hDlg, pszName);
        }
    }
    else
    {
        ConfigureDriver (hDlg, pszName);
    }
}


void WINAPI ShowDriverSettingsAfterFork (
                                        HWND hwndStub,
                                        HINSTANCE hAppInstance,
                                        LPTSTR lpszCmdLine,
                                        int nCmdShow)
{
    #ifdef UNICODE
    WCHAR szCmdLine[ cchRESOURCE ];
    #else
        #define szCmdLine lpszCmdLine
    #endif

    lstrcpy (szDriverWhichNeedsSettings, szCmdLine);
    cplMMDoubleClick (NULL, 4); // 4==Start on Advanced ("Devices") tab
}

void WINAPI ShowDriverSettingsAfterForkW (
                                         HWND hwndStub,
                                         HINSTANCE hAppInstance,
                                         LPWSTR lpwszCmdLine,
                                         int nCmdShow)
{
    #ifdef UNICODE
        #define szCmdLine lpwszCmdLine
    #else
    CHAR szCmdLine[ cchRESOURCE ];
    wcstombs(szCmdLine, lpwszCmdLine, cchRESOURCE);
    #endif

    lstrcpy (szDriverWhichNeedsSettings, szCmdLine);
    cplMMDoubleClick (NULL, 4); // 4==Start on Advanced ("Devices") tab
}

#endif // FIX_BUG_15451

// Globals to support sound event command line parameters.

#define MAX_SND_EVNT_CMD_LINE 32
TCHAR    gszCmdLineApp[MAX_SND_EVNT_CMD_LINE];
TCHAR    gszCmdLineEvent[MAX_SND_EVNT_CMD_LINE];

/*==========================================================================*/
LONG CPlApplet(
              HWND    hCPlWnd,
              UINT    Msg,
              LPARAM  lParam1,
              LPARAM  lParam2)
{
    switch (Msg)
    {
    case CPL_INIT:
        wHelpMessage = RegisterWindowMessage(TEXT("ShellHelp"));
        DPF_T("*CPL_INIT*");
        g_cRefCnt++;
        return (LRESULT)TRUE;

    case CPL_GETCOUNT:
        return (LRESULT)1;

    case CPL_INQUIRE:
        DPF_T("*CPL_INQUIRE*");
        switch (lParam1)
        {
        case 0:
            ((LPCPLINFO)lParam2)->idIcon = IDI_MMICON;
            ((LPCPLINFO)lParam2)->idName = IDS_MMNAME;
            ((LPCPLINFO)lParam2)->idInfo = IDS_MMINFO;
            break;
        default:
            return FALSE;
        }
        ((LPCPLINFO)lParam2)->lData = 0L;
        return TRUE;

    case CPL_NEWINQUIRE:
        switch (lParam1)
        {
        case 0:
            ((LPNEWCPLINFO)lParam2)->hIcon = LoadIcon(ghInstance, MAKEINTRESOURCE(IDI_MMICON));
            LoadString(ghInstance, IDS_MMNAME, ((LPNEWCPLINFO)lParam2)->szName, sizeof(((LPNEWCPLINFO)lParam2)->szName)/sizeof(TCHAR));
            LoadString(ghInstance, IDS_MMINFO, ((LPNEWCPLINFO)lParam2)->szInfo, sizeof(((LPNEWCPLINFO)lParam2)->szInfo)/sizeof(TCHAR));
            break;
        default:
            return FALSE;
        }
        ((LPNEWCPLINFO)lParam2)->dwHelpContext = 0;
        ((LPNEWCPLINFO)lParam2)->dwSize = sizeof(NEWCPLINFO);
        ((LPNEWCPLINFO)lParam2)->lData = 0L;
        ((LPNEWCPLINFO)lParam2)->szHelpFile[0] = 0;
        return TRUE;

    case CPL_DBLCLK:
        DPF_T("* CPL_DBLCLICK*");
        // Do the applet thing.
        switch (lParam1)
        {
        case 0:
            // Check for obsolete command line (see comments
            // under CPL_STARTWPARAMS)
            if ((-1) == g_nStartPage) break;

#ifdef FIX_BUG_15451
            lstrcpy (szDriverWhichNeedsSettings, TEXT(""));
            cplMMDoubleClick(hCPlWnd, g_nStartPage);
#else // FIX_BUG_15451
            cplMMDoubleClick(hCPlWnd);
#endif // FIX_BUG_15451
            break;
        }
        break;

    case CPL_STARTWPARMS:
        switch (lParam1)
        {
        case 0:
            if (lParam2 && *((LPTSTR)lParam2))
            {
                TCHAR c;

                c = *((LPTSTR)lParam2);
                if (c > TEXT('0') && c < TEXT('5'))
                {
                    g_nStartPage = c - TEXT('0');
                    break;
                }

                // The "S" command line was used on Windows 98 and Windows 98
                // SE.  The command line was written to the Active Setup
                // registry to run logic to setup preferred devices on a
                // specific PnP device instance.  It is obsolete and handled in
                // winmm.dll itself.  This cpl should do nothing for this
                // command line.
                if ((c == TEXT('S')) || (c == TEXT('s'))) {
                    g_nStartPage = (-1);
                    break;
                }
            }
            g_nStartPage = 0;
            break;

            // For sound events, the passed in parameter indicates a module
            // name and event. If a name is passed only show it's sound events.
            // If a name and event are passed only show the event.
            /*        case 1:

                        if (lParam2 && *((LPTSTR)lParam2))
                        {
                            TCHAR *psz;

                            if ((psz = wcschr((LPTSTR)lParam2, TEXT(','))) != NULL)
                            {
                                *psz++ = TEXT('\0');
                                wcsncpy(gszCmdLineEvent, psz, MAX_SND_EVNT_CMD_LINE/sizeof(TCHAR));
                                gszCmdLineEvent[MAX_SND_EVNT_CMD_LINE-sizeof(TCHAR)] = TEXT('\0');
                            }
                            wcsncpy(gszCmdLineApp, (LPTSTR)lParam2,
                                    MAX_SND_EVNT_CMD_LINE/sizeof(TCHAR));
                            gszCmdLineApp[MAX_SND_EVNT_CMD_LINE-sizeof(TCHAR)] = TEXT('\0');
                        }
                        break; */
        }

        break;

    case CPL_EXIT:
        DPF_T("* CPL_EXIT*");
        g_cRefCnt--;
        break;
    }
    return 0;
}


void PASCAL ShowPropSheet(LPCTSTR            pszTitle,
                          DLGPROC            pfnDialog,
                          UINT            idTemplate,
                          HWND            hWndParent,
                          LPTSTR            pszCaption,
                          LPARAM lParam)
{
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE  hpsp[MAXPAGES];


    InitPSH(&psh,hWndParent,pszCaption,hpsp);
    AddPage(&psh, pszTitle,  pfnDialog, idTemplate, lParam);
    PropertySheet(&psh);

}

void PASCAL ShowMidiPropSheet(LPPROPSHEETHEADER ppshExt,
                              LPCTSTR    pszTitle,
                              HWND      hWndParent,
                              short     iMidiPropType,
                              LPTSTR     pszCaption,
                              HTREEITEM hti,
                              LPARAM    lParam1,
                              LPARAM    lParam2)
{
    PROPSHEETHEADER psh;
    LPPROPSHEETHEADER ppsh;
    HPROPSHEETPAGE  hpsp[MAXPAGES];
    static EXTPROPSHEETCBPARAM cbp;

    if (!ppshExt)
    {
        ppsh = &psh;
        InitPSH(ppsh,hWndParent,pszCaption,hpsp);
    }
    else
        ppsh = ppshExt;

    cbp.lParam1 = lParam1;
    cbp.lParam2 = lParam2;
    cbp.hti = hti;
    cbp.ppsh = ppsh;

    if (iMidiPropType == MIDI_CLASS_PROP)
    {
        if (AddMidiPages((LPVOID)pszTitle, MMExtPropSheetCallback, (LPARAM)&cbp))
        {
            PropertySheet(ppsh);
        }
    }
    else if (iMidiPropType == MIDI_INSTRUMENT_PROP)
    {
        if (AddInstrumentPages((LPVOID)pszTitle, MMExtPropSheetCallback, (LPARAM)&cbp))
        {
            PropertySheet(ppsh);
        }
    }
    else
    {
        if (AddDevicePages((LPVOID)pszTitle, MMExtPropSheetCallback, (LPARAM)&cbp))
        {
            PropertySheet(ppsh);
        }
    }
}

void PASCAL ShowWithMidiDevPropSheet(LPCTSTR            pszTitle,
                                     DLGPROC            pfnDialog,
                                     UINT            idTemplate,
                                     HWND            hWndParent,
                                     LPTSTR            pszCaption,
                                     HTREEITEM    hti,
                                     LPARAM lParam, LPARAM lParamExt1, LPARAM lParamExt2)
{
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE  hpsp[MAXPAGES];


    InitPSH(&psh,hWndParent,pszCaption,hpsp);
    AddPage(&psh, pszTitle,  pfnDialog, idTemplate, lParam);
    PropertySheet(&psh);
    // Disabling the details sheet - obsolete 01/10/2001
    //ShowMidiPropSheet(&psh, pszCaption, hWndParent,MIDI_DEVICE_PROP,pszCaption,hti,lParamExt1,lParamExt2);
}

BOOL WINAPI ShowMMCPLPropertySheetW(HWND hwndParent, LPCTSTR pszPropSheetID, LPTSTR pszTabName, LPTSTR pszCaption)
{
    DLGPROC pfnDlgProc;
    UINT    idTemplate;
    HWND    hwndP;
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE  hpsp[MAXPAGES];

    if (GetWindowLongPtr(hwndParent, GWL_EXSTYLE) & WS_EX_TOPMOST)
        hwndP = NULL;
    else
        hwndP = hwndParent;

    InitPSH(&psh,hwndP,pszCaption,hpsp);
    psh.dwFlags = 0;

    if (!lstrcmpi(pszPropSheetID, cszAUDIO))
    {
        pfnDlgProc = AudioDlg;
        idTemplate = AUDIODLG;
        goto ShowSheet;
    }
	if (!lstrcmpi(pszPropSheetID, cszVOICE))
    {
        pfnDlgProc = VoiceDlg;
        idTemplate = VOICEDLG;
        goto ShowSheet;
    } 
	if (!lstrcmpi(pszPropSheetID, cszVOLUME))
    {
        pfnDlgProc = VolumeDlg;
        idTemplate = IDD_VOLUME;
        goto ShowSheet;
    } 
    if (!lstrcmpi(pszPropSheetID, cszVIDEO))
    {
        pfnDlgProc = VideoDlg;
        idTemplate = VIDEODLG;
        goto ShowSheet;
    }
    if (!lstrcmpi(pszPropSheetID, cszCDAUDIO))
    {
        pfnDlgProc = CD_HardwareDlgProc;
        idTemplate = HWDLG;
        goto ShowSheet;
    }
    if (!lstrcmpi(pszPropSheetID, cszMIDI))
    {
    /*
        static EXTPROPSHEETCBPARAM cbpMIDI;

        cbpMIDI.ppsh = &psh;
        cbpMIDI.hti = NULL;
        cbpMIDI.lParam1 = (LPARAM)pszPropSheetID;
        cbpMIDI.lParam2 = (LPARAM)pszTabName;
        AddSimpleMidiPages((LPVOID)pszTabName, MMExtPropSheetCallback, (LPARAM)&cbpMIDI);
        PropertySheet(&psh);
        return TRUE;
      */

        pfnDlgProc = AudioDlg;
        idTemplate = AUDIODLG;
        goto ShowSheet;

    }

    return FALSE;
    ShowSheet:
    AddPage(&psh, pszTabName,  pfnDlgProc, idTemplate, (LPARAM)NULL);
    PropertySheet(&psh);
    return TRUE;
}

BOOL WINAPI ShowMMCPLPropertySheet(HWND hwndParent, LPCSTR pszPropSheetID, LPSTR pszTabName, LPSTR pszCaption)
{
    DLGPROC pfnDlgProc;
    UINT    idTemplate;
    HWND    hwndP;
    PROPSHEETHEADER psh;
    HPROPSHEETPAGE  hpsp[MAXPAGES];
    TCHAR szPropSheetID[MAX_PATH];
    TCHAR szTabName[MAX_PATH];
    TCHAR szCaption[MAX_PATH];

    //convert three params into UNICODE strings
    MultiByteToWideChar( GetACP(), 0, pszPropSheetID, -1, szPropSheetID, sizeof(szPropSheetID) / sizeof(TCHAR) );
    MultiByteToWideChar( GetACP(), 0, pszTabName,     -1, szTabName,     sizeof(szTabName)     / sizeof(TCHAR) );
    MultiByteToWideChar( GetACP(), 0, pszCaption,     -1, szCaption,     sizeof(szCaption)     / sizeof(TCHAR) );

    return (ShowMMCPLPropertySheetW(hwndParent,szPropSheetID,szTabName,szCaption));
}

//allows you to show control panel from RUNDLL32
DWORD WINAPI ShowFullControlPanel(HWND hwndP, HINSTANCE hInst, LPTSTR szCmd, int nShow)
{
    cplMMDoubleClick(hwndP, 0);
    return 0;
}

DWORD WINAPI ShowAudioPropertySheet(HWND hwndP, HINSTANCE hInst, LPTSTR szCmd, int nShow)
{
    TCHAR szAudio[MAXLNAME];
    TCHAR szAudioProperties[MAXLNAME];
    char mbcszAUDIO[MAXLNAME];
    char mbszAudio[MAXLNAME];
    char mbszAudioProperties[MAXLNAME];
    HWND hwndPrev;

    LoadString(ghInstance, IDS_AUDIOPROPERTIES, szAudioProperties, sizeof(szAudioProperties)/sizeof(TCHAR));
    hwndPrev = FindWindow(NULL,szAudioProperties);
    if (hwndPrev)
    {
        SetForegroundWindow(hwndPrev);
    }
    else
    {
        LoadString(ghInstance, IDS_WAVE_HEADER, szAudio, sizeof(szAudio)/sizeof(TCHAR));
        ShowMMCPLPropertySheetW(hwndP, cszAUDIO, szAudio, szAudioProperties);
    }
    return 0;
}


DWORD WINAPI mmseRunOnce(HWND hwnd, HINSTANCE hInst, LPSTR lpszCmdLine, int nShow)
{
    // This is an obsolete function formerly used to migrate
    // registry and driver information.  We leave the export
    // in place in case an old installation has left a RunOnce
    // command in place to execute this function.
    return 0;
}

DWORD WINAPI mmseRunOnceW(HWND hwnd, HINSTANCE hInst, LPWSTR lpwszCmdLine, int nShow)
{
    // This is an obsolete function formerly used to migrate
    // registry and driver information.  We leave the export
    // in place in case an old installation has left a RunOnce
    // command in place to execute this function.
    return 0;
}

extern BOOL DriversDllInitialize (IN PVOID, IN DWORD, IN PCONTEXT OPTIONAL);

BOOL DllInitialize (IN PVOID hInstance,
                    IN DWORD ulReason,
                    IN PCONTEXT pctx OPTIONAL)
{
    // patch in the old DRIVERS.DLL code (see DRIVERS.C)
    //
    DriversDllInitialize (hInstance, ulReason, pctx);

    if (ulReason == DLL_PROCESS_ATTACH)
    {
        ++g_cProcesses;
        ghInstance = hInstance;
        DisableThreadLibraryCalls(hInstance);
        return TRUE;
    }

    if (ulReason == DLL_PROCESS_DETACH)
    {
        --g_cProcesses;
        return TRUE;
    }

    return TRUE;
}

DWORD
    WINAPI
    MediaClassInstaller(
                       IN DI_FUNCTION      InstallFunction,
                       IN HDEVINFO         DeviceInfoSet,
                       IN PSP_DEVINFO_DATA DeviceInfoData OPTIONAL
                       )
/*++

Routine Description:

    This routine acts as the class installer for Media devices.

Arguments:

    InstallFunction - Specifies the device installer function code indicating
        the action being performed.

    DeviceInfoSet - Supplies a handle to the device information set being
        acted upon by this install action.

    DeviceInfoData - Optionally, supplies the address of a device information
        element being acted upon by this install action.

Return Value:

    If this function successfully completed the requested action, the return
        value is NO_ERROR.

    If the default behavior is to be performed for the requested action, the
        return value is ERROR_DI_DO_DEFAULT.

    If an error occurred while attempting to perform the requested action, a
        Win32 error code is returned.

--*/
{
    DWORD dwRet=ERROR_DI_DO_DEFAULT;

    switch (InstallFunction)
    {

    case DIF_SELECTBESTCOMPATDRV:
        dwRet = Media_SelectBestCompatDrv(DeviceInfoSet,DeviceInfoData);
        break;

    case DIF_ALLOW_INSTALL:
        dwRet = Media_AllowInstall(DeviceInfoSet,DeviceInfoData);
        break;

    case DIF_INSTALLDEVICE :
        dwRet = Media_InstallDevice(DeviceInfoSet, DeviceInfoData);
        break;

    case DIF_REMOVE:
        dwRet = Media_RemoveDevice(DeviceInfoSet,DeviceInfoData);
        break;

    case DIF_SELECTDEVICE:
        dwRet = Media_SelectDevice(DeviceInfoSet,DeviceInfoData);
        break;

    case DIF_FIRSTTIMESETUP:
        // Fall through

    case DIF_DETECT:
        dwRet = Media_MigrateLegacy(DeviceInfoSet,DeviceInfoData);
        break;

    }

    return dwRet;

}

DWORD WINAPI mmWOW64MediaInstallDevice(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData)
{
    SP_DEVINSTALL_PARAMS DeviceInstallParams;
    HWND hWnd;

    //
    // Get the device install parameters, so we'll know what parent window to use for any
    // UI that occurs during configuration of this device.
    //
    DeviceInstallParams.cbSize = sizeof(DeviceInstallParams);
    if (SetupDiGetDeviceInstallParams(DeviceInfoSet, DeviceInfoData, &DeviceInstallParams))
    {
        hWnd = DeviceInstallParams.hwndParent;
    }
    else
    {
        hWnd = NULL;
    }

    //
    // The INF will have created a "Drivers" subkey under the device's software key.
    // This tree, in turn, contains subtrees for each type of driver (aux, midi, etc.)
    // applicable for this device.  We must now traverse this tree, and create entries
    // in Drivers32 for each function alias.
    //
    return InstallDriversForPnPDevice(hWnd, DeviceInfoSet, DeviceInfoData);
}


DWORD WINAPI mmWOW64MediaRemoveDevice(HDEVINFO DeviceInfoSet, PSP_DEVINFO_DATA DeviceInfoData)
{
    if( RemoveDriver(DeviceInfoSet, DeviceInfoData) )
    {
        return NO_ERROR;
    }
    else
    {
        return ERROR_BAD_DRIVER;
    }
}


DWORD WINAPI mmWOW64MediaClassInstallerA(HWND hwnd, HINSTANCE hInst, LPSTR lpszCmdLine, int nShow)
/*++

Routine Description:

    This routine acts as a thunking layer for calling the 32-bit
        installation functions from the 64-bit setup via RunDLL32.exe.

Arguments:

    hwnd - not used

    hInst - not used

    lpwszCmdLine - command line arguments: "Instance ID (string)" DI_FUNCTION (as an integer)

    nShow - not used

Return Value:

    If this function successfully completed the requested action, the return
        value is NO_ERROR.

    If an error occurred while attempting to perform the requested action, a
        Win32 error code is returned.

--*/
{
    LPSTR strInstanceID = NULL;
    LPSTR strInstallIndex = NULL;
    DWORD dwInstallIndex = 0;
    LPSTR strTemp = NULL;
    HDEVINFO DeviceInfoSet = NULL;
    SP_DEVINFO_DATA DeviceInfoData;
    DWORD dwResult = NO_ERROR;

    // Find first quote
    strTemp = strchr( lpszCmdLine, '\"' );
    if( !strTemp )
    {
        return ERROR_INVALID_PARAMETER;
    }


    // Instance ID

    // Skip first quote
    strInstanceID = ++strTemp;

    // Find second quote
    strTemp = strchr( strTemp, '\"' );
    if( !strTemp )
    {
        return ERROR_INVALID_PARAMETER;
    }

    // NULL-terminate the InstanceID
    *strTemp = 0;


    // Install Index

    // Skip the NULL
    strInstallIndex = ++strTemp;


    // Convert the installation index
    dwInstallIndex = atoi( strInstallIndex );


    // Create a device handle
    DeviceInfoSet = SetupDiCreateDeviceInfoList( NULL, NULL );
    if( INVALID_HANDLE_VALUE == DeviceInfoSet )
    {
        return ERROR_NOT_ENOUGH_MEMORY;
    }

    // Create the device info structure
    ZeroMemory( &DeviceInfoData, sizeof(SP_DEVINFO_DATA) );
    DeviceInfoData.cbSize = sizeof(SP_DEVINFO_DATA);
    if( 0 == SetupDiOpenDeviceInfoA( DeviceInfoSet, strInstanceID, NULL, 0, &DeviceInfoData ) )
    {
        dwResult = GetLastError();
    }

    // Do the installation task
    if( NO_ERROR == dwResult )
    {
        switch( dwInstallIndex )
        {
        case DIF_INSTALLDEVICE:
            dwResult = mmWOW64MediaInstallDevice(DeviceInfoSet, &DeviceInfoData);
            break;
        case DIF_REMOVE:
            dwResult = mmWOW64MediaRemoveDevice(DeviceInfoSet, &DeviceInfoData);
            break;
        default:
            dwResult = ERROR_INVALID_PARAMETER;
            break;
        }
    }

    SetupDiDestroyDeviceInfoList( DeviceInfoSet );

    return dwResult;
}