/*******************************Module*Header*********************************\
* Module Name: mcisys.c
*
* Media Control Architecture System Functions
*
* Created: 2/28/90
* Author:  DLL (DavidLe)
*
* History:
*
* Copyright (c) 1990 Microsoft Corporation
*
\******************************************************************************/

#include <windows.h>

#define MMNOMIDI
#define MMNOWAVE
#define MMNOSOUND
#define MMNOTIMER
#define MMNOJOY
#define MMNOSEQ
#include "mmsystem.h"
#define NOMIDIDEV
#define NOWAVEDEV
#define NOTIMERDEV
#define NOJOYDEV
#define NOSEQDEV
#define NOTASKDEV
#include "mmddk.h"
#include "mmsysi.h"
#include "thunks.h"

#ifndef STATICFN
#define STATICFN
#endif

extern char far szOpen[];          // in MCI.C

static SZCODE szNull[] = "";
static SZCODE szMciExtensions[] = "mci extensions";

#define MCI_EXTENSIONS szMciExtensions
#define MCI_PROFILE_STRING_LENGTH 255

//!!#define TOLOWER(c) ((c) >= 'A' && (c) <= 'Z' ? (c) + 'a' - 'A' : c)

// The device list is initialized on the first call to mciSendCommand or
// to mciSendString
BOOL MCI_bDeviceListInitialized;

// The next device ID to use for a new device
UINT MCI_wNextDeviceID = 1;

// The list of MCI devices. This list grows and shrinks as needed.
// The first offset MCI_lpDeviceList[0] is a placeholder and is unused
// because device 0 is defined as no device.
LPMCI_DEVICE_NODE FAR * MCI_lpDeviceList;

// The current size of the list of MCI devices
UINT MCI_wDeviceListSize;

// The internal mci heap used by mciAlloc and mciFree
HGLOBAL hMciHeap;

// File containing MCI device profile strings
extern char far szSystemIni[];			// in INIT.C

// Name of the section contining MCI device profile strings
static SZCODE szMCISectionName[] = "mci";

static SZCODE szAllDeviceName[] = "all";

static SZCODE szUnsignedFormat[] = "%u";

static void PASCAL NEAR mciFreeDevice(LPMCI_DEVICE_NODE nodeWorking);

BOOL NEAR PASCAL CouldBe16bitDrv(UINT wDeviceID)
{
    if (wDeviceID == MCI_ALL_DEVICE_ID) return TRUE;

    if (MCI_VALID_DEVICE_ID(wDeviceID)) {
        if (MCI_lpDeviceList[wDeviceID]->dwMCIFlags & MCINODE_16BIT_DRIVER) {
            return TRUE;
        }
    }
    return FALSE;
}

BOOL NEAR PASCAL Is16bitDrv(UINT wDeviceID)
{
    if (wDeviceID == MCI_ALL_DEVICE_ID) return FALSE;

    if (MCI_VALID_DEVICE_ID(wDeviceID)) {
        if (MCI_lpDeviceList[wDeviceID]->dwMCIFlags & MCINODE_16BIT_DRIVER) {
            return TRUE;
        }
    }
    return FALSE;
}

//
// Initialize device list
// Called once by mciSendString or mciSendCommand
// Returns TRUE on success
BOOL NEAR PASCAL mciInitDeviceList(void)
{

    if ((hMciHeap = HeapCreate(0)) == 0)
    {
        DOUT("Mci heap create failed!\r\n");
        return FALSE;
    }
    if ((MCI_lpDeviceList = mciAlloc (sizeof (LPMCI_DEVICE_NODE) *
                                  (MCI_INIT_DEVICE_LIST_SIZE + 1))) != NULL)
    {
        MCI_wDeviceListSize = MCI_INIT_DEVICE_LIST_SIZE;
        MCI_bDeviceListInitialized = TRUE;
        return TRUE;
    } else
    {
        DOUT ("MCIInit: could not allocate master MCI device list\r\n");
        return FALSE;
    }
}

/*
 * @doc EXTERNAL MCI
 * @api UINT | mciGetDeviceIDFromElementID | This function
 * retrieves the MCI device ID corresponding to and element ID
 *
 * @parm DWORD | dwElementID | The element ID
 *
 * @parm LPCSTR | lpstrType | The type name this element ID belongs to
 *
 * @rdesc Returns the device ID assigned when it was opened and used in the
 * <f mciSendCommand> function.  Returns zero if the device name was not known,
 * if the device was not open, or if there was not enough memory to complete
 * the operation or if lpstrType is NULL.
 *
 */
UINT WINAPI mciGetDeviceIDFromElementID (
DWORD dwElementID,
LPCSTR lpstrType)
{
    UINT wID;
    LPMCI_DEVICE_NODE nodeWorking, FAR *nodeCounter;
    char strTemp[MCI_MAX_DEVICE_TYPE_LENGTH];

    if (lpstrType == NULL)
        return 0;

    wID = (UINT)mciMessage( THUNK_MCI_GETDEVIDFROMELEMID, dwElementID,
                            (DWORD)lpstrType, 0L, 0L );
    if ( wID == 0 ) {

        nodeCounter = &MCI_lpDeviceList[1];

        for (wID = 1; wID < MCI_wNextDeviceID; ++wID)
        {
            nodeWorking = *nodeCounter++;

            if (nodeWorking == NULL)
                continue;

            if (nodeWorking->dwMCIOpenFlags & MCI_OPEN_ELEMENT_ID &&
                nodeWorking->dwElementID == dwElementID)

                if (LoadString (ghInst, nodeWorking->wDeviceType, strTemp,
                                sizeof(strTemp)) != 0
                    && lstrcmpi ((LPSTR)strTemp, lpstrType) == 0) {

                    return (wID);
                }
        }
        return 0;
    }
    return wID;
}

// Retrieves the device ID corresponding to the name of an opened device
// matching the given task
// This fn only looks for 16-bit devices
// See mciGetDeviceIDInternalEx that looks for all of them
UINT NEAR PASCAL mciGetDeviceIDInternal (
LPCSTR lpstrName,
HTASK hTask)
{
    UINT wID;
    LPMCI_DEVICE_NODE nodeWorking, FAR *nodeCounter;

    if (lstrcmpi (lpstrName, szAllDeviceName) == 0)
        return MCI_ALL_DEVICE_ID;

    if (MCI_lpDeviceList == NULL)
        return 0;

// Loop through the MCI device list
    nodeCounter = &MCI_lpDeviceList[1];
    for (wID = 1; wID < MCI_wNextDeviceID; ++wID)
    {
        nodeWorking = *nodeCounter++;

        if (nodeWorking == NULL)
            continue;

// If this device does not have a name then skip it
        if (nodeWorking->dwMCIOpenFlags & MCI_OPEN_ELEMENT_ID)
            continue;

// If the names match
        if (lstrcmpi(nodeWorking->lpstrName, lpstrName) == 0)

// If the device belongs to the indicated task
            if (nodeWorking->hOpeningTask == hTask)
// Return this device ID
                return wID;
    }

    return 0;
}

/*
 * @doc EXTERNAL MCI
 * @api UINT | mciGetDeviceID | This function retrieves the device
 * ID corresponding to the name of an open MCI device.
 *
 * @parm LPCSTR | lpstrName | Specifies the device name used to open the
 * MCI device.
 *
 * @rdesc Returns the device ID assigned when the device was opened.
 * Returns zero if the device name isn't known,
 * if the device isn't open, or if there was insufficient memory to complete
 * the operation.  Each compound device element has a unique device ID.
 * The ID of the "all" device is MCI_ALL_DEVICE_ID.
 *
 * @xref MCI_OPEN
 *
 */
UINT WINAPI mciGetDeviceID (
LPCSTR lpstrName)
{
    UINT    wDevID;

    /*
    ** Try the 32 bit side first
    */
    wDevID = (UINT)mciMessage( THUNK_MCI_GETDEVICEID, (DWORD)lpstrName,
                               0L, 0L, 0L );
    if ( wDevID == 0 ) {

        /*
        ** The 32 bit call failed so let the 16 bit side have a go.
        */
        wDevID = mciGetDeviceIDInternal (lpstrName, GetCurrentTask());

    }

    return wDevID;
}

//
//  This function is same as mciGetDeviceID but it won't call GetCurrentTask
//  Used when mci needs to verify the dev alias had not been allocated yet
//
//

UINT NEAR PASCAL mciGetDeviceIDInternalEx(
LPCSTR lpstrName,
HTASK hTask)
{
    UINT uiDevID;

    uiDevID = (UINT)mciMessage( THUNK_MCI_GETDEVICEID, (DWORD)lpstrName,
                                0L, 0L, 0L );
    if (0 == uiDevID) {

        uiDevID = mciGetDeviceIDInternal(lpstrName, hTask);
    }

    return uiDevID;
}


/*
 * @doc EXTERNAL MCI
 * @api HTASK | mciGetCreatorTask | This function retrieves the creator task
 * corresponding with the device ID passed.
 *
 * @parm UINT | wDeviceID | Specifies the device ID whose creator task is to
 * be returned.
 *
 * @rdesc Returns the creator task responsible for opening the device, else
 * NULL if the device ID passed is invalid.
 *
 */
HTASK WINAPI mciGetCreatorTask (
UINT wDeviceID)
{
    /*
    ** Is this a 16 bit device ID
    */
    if (Is16bitDrv(wDeviceID)) {

        return MCI_lpDeviceList[wDeviceID]->hCreatorTask;
    }

    /*
    ** No, so pass it on to the 32 bit code.
    */

    return (HTASK)mciMessage( THUNK_MCI_GETCREATORTASK, (DWORD)wDeviceID,
                              0L, 0L, 0L );
}

/*
 * @doc INTERNAL MCI
 * @func BOOL | mciDeviceMatch | Match the first string with the second.
 * Any single trailing digit on the first string is ignored.  Each string
 * must have at least one character
 *
 * @parm LPCSTR | lpstrDeviceName | The device name, possibly
 * with trailing digits but no blanks.
 *
 * @parm LPCSTR | lpstrDeviceType | The device type with no trailing digits
 * or blanks
 *
 * @rdesc TRUE if the strings match the above test, FALSE otherwise
 *
 */
STATICFN BOOL PASCAL NEAR
mciDeviceMatch(
    LPCSTR lpstrDeviceName,
    LPCSTR lpstrDeviceType
    )
{
    BOOL bAtLeastOne;

    for (bAtLeastOne = FALSE;;)
        if (!*lpstrDeviceType)
            break;
        else if (!*lpstrDeviceName || ((BYTE)(WORD)(DWORD)AnsiLower((LPSTR)(DWORD)(WORD)(*lpstrDeviceName++)) != (BYTE)(WORD)(DWORD)AnsiLower((LPSTR)(DWORD)(WORD)(*lpstrDeviceType++))))
            return FALSE;
        else
            bAtLeastOne = TRUE;
    if (!bAtLeastOne)
        return FALSE;
    for (; *lpstrDeviceName; lpstrDeviceName++)
        if ((*lpstrDeviceName < '0') || (*lpstrDeviceName > '9'))
            return FALSE;
    return TRUE;
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciLookUpType | Look up the type given a type name
 *
 * @parm LPCSTR | lpstrTypeName | The type name to look up.  Trailing
 * digits are ignored.
 *
 * @rdesc The MCI type number (MCI_DEVTYPE_<x>) or 0 if not found
 *
!! * @comm Converts the input string to lower case as a side effect
 *
 */
UINT PASCAL NEAR mciLookUpType (
LPCSTR lpstrTypeName)
{
    UINT wType;
    char strType[MCI_MAX_DEVICE_TYPE_LENGTH];

//!!    mciToLower (lpstrTypeName);

    for (wType = MCI_DEVTYPE_FIRST; wType <= MCI_DEVTYPE_LAST; ++wType)
    {
        if (LoadString (ghInst, wType, strType, sizeof(strType)) == 0)
        {
            DOUT ("mciLookUpType:  could not load string for type\r\n");
            continue;
        }

        if (mciDeviceMatch (lpstrTypeName, strType))
            return wType;
    }
    return 0;
}

/*
 * @doc INTERNAL MCI
 * @func DWORD | mciSysinfo | Get system information about a device
 *
 * @parm UINT | wDeviceID | Device ID, may be 0
 *
 * @parm DWORD | dwFlags | SYSINFO flags
 *
 * @parm LPMCI_SYSINFO_PARMS | lpSysinfo | SYSINFO parameters
 *
 * @rdesc 0 if successful, otherwise error code
 *
 */
DWORD PASCAL NEAR mciSysinfo (
UINT wDeviceID,
DWORD dwFlags,
LPMCI_SYSINFO_PARMS lpSysinfo)
{
    UINT wCounted;
    char              strBuffer[MCI_PROFILE_STRING_LENGTH];
    LPSTR             lpstrBuffer = (LPSTR)strBuffer, lpstrStart;

    if (dwFlags & MCI_SYSINFO_NAME && lpSysinfo->dwNumber == 0)
        return MCIERR_OUTOFRANGE;

    if (lpSysinfo->lpstrReturn == NULL || lpSysinfo->dwRetSize == 0)
        return MCIERR_PARAM_OVERFLOW;

    if (dwFlags & MCI_SYSINFO_NAME && dwFlags & MCI_SYSINFO_QUANTITY)
        return MCIERR_FLAGS_NOT_COMPATIBLE;

    if (dwFlags & MCI_SYSINFO_INSTALLNAME)
    {
        LPMCI_DEVICE_NODE nodeWorking;

        if (wDeviceID == MCI_ALL_DEVICE_ID)
            return MCIERR_CANNOT_USE_ALL;
        if (!MCI_VALID_DEVICE_ID(wDeviceID))
            return MCIERR_INVALID_DEVICE_NAME;

        nodeWorking = MCI_lpDeviceList[wDeviceID];
        if ((DWORD)lstrlen (nodeWorking->lpstrInstallName) >= lpSysinfo->dwRetSize)
            return MCIERR_PARAM_OVERFLOW;
        lstrcpy (lpSysinfo->lpstrReturn, nodeWorking->lpstrInstallName);
        return 0;
    } else if (!(dwFlags & MCI_SYSINFO_OPEN))
    {
        if (wDeviceID != MCI_ALL_DEVICE_ID && lpSysinfo->wDeviceType == 0)
            return MCIERR_DEVICE_TYPE_REQUIRED;

        if ((dwFlags & (MCI_SYSINFO_QUANTITY | MCI_SYSINFO_NAME)) == 0)
            return MCIERR_MISSING_PARAMETER;
        GetPrivateProfileString (szMCISectionName, NULL, szNull,
                                 lpstrBuffer, MCI_PROFILE_STRING_LENGTH,
                                 szSystemIni);
        wCounted = 0;
        while (TRUE)
        {
            if (dwFlags & MCI_SYSINFO_QUANTITY)
            {
                if (*lpstrBuffer == '\0')
                {
                    *(LPDWORD)lpSysinfo->lpstrReturn = (DWORD)wCounted;
                    return MCI_INTEGER_RETURNED;
                }
                if (wDeviceID == MCI_ALL_DEVICE_ID ||
                    mciLookUpType (lpstrBuffer) == lpSysinfo->wDeviceType)
                    ++wCounted;
// Skip past the terminating '\0'
                while (*lpstrBuffer != '\0')
                    *lpstrBuffer++;
                lpstrBuffer++;
            } else if (dwFlags & MCI_SYSINFO_NAME)
            {
                if (wCounted == (UINT)lpSysinfo->dwNumber)
                {
                    lstrcpy (lpSysinfo->lpstrReturn, lpstrStart);
                    return 0L;
                } else if (*lpstrBuffer == '\0')
                    return MCIERR_OUTOFRANGE;
                else
                {
                    lpstrStart = lpstrBuffer;
                    if (wDeviceID == MCI_ALL_DEVICE_ID ||
                        mciLookUpType (lpstrBuffer) == lpSysinfo->wDeviceType)
                        ++wCounted;
// Skip past the terminating '\0'
                    while (*lpstrBuffer != '\0')
                        *lpstrBuffer++;
                    lpstrBuffer++;
                }
            }
        }
    } else
// Process MCI_SYSINFO_OPEN cases
    {
        UINT wID;
        HTASK hCurrentTask = GetCurrentTask();
        LPMCI_DEVICE_NODE Node;

        if (wDeviceID != MCI_ALL_DEVICE_ID &&
            lpSysinfo->wDeviceType == 0)
            return MCIERR_DEVICE_TYPE_REQUIRED;

        if ((dwFlags & (MCI_SYSINFO_QUANTITY | MCI_SYSINFO_NAME)) == 0)
            return MCIERR_MISSING_PARAMETER;

        wCounted = 0;
        for (wID = 1; wID < MCI_wNextDeviceID; ++wID)
        {
            if ((Node = MCI_lpDeviceList[wID]) == 0)
                continue;

            if (wDeviceID == MCI_ALL_DEVICE_ID &&
                Node->hOpeningTask == hCurrentTask)
                ++wCounted;
            else
            {
                if (Node->wDeviceType == lpSysinfo->wDeviceType &&
                    Node->hOpeningTask == hCurrentTask)
                    ++wCounted;
            }
            if (dwFlags & MCI_SYSINFO_NAME &&
                wCounted == (UINT)lpSysinfo->dwNumber)
            {
                lstrcpy (lpSysinfo->lpstrReturn, Node->lpstrName);
                return 0L;
            }
        }
        if (dwFlags & MCI_SYSINFO_NAME)
        {
            if (lpSysinfo->lpstrReturn != NULL)
                lpSysinfo->lpstrReturn = '\0';
            return MCIERR_OUTOFRANGE;

        } else if (dwFlags & MCI_SYSINFO_QUANTITY &&
                   lpSysinfo->lpstrReturn != NULL &&
                   lpSysinfo->dwRetSize >= 4)

            *(LPDWORD)lpSysinfo->lpstrReturn = wCounted;
    }
    return MCI_INTEGER_RETURNED;
}

/*
 * @doc INTERNAL MCI
 * @func UINT | wAddDeviceNodeToList | Add the given global handle into the
 * MCI device table and return that entry's ID#
 *
 * @parm LPMCI_DEVICE_NODE | node | device description
 *
 * @rdesc The ID value for this device or 0 if there is no memory to expand
 * the device list
 *
 */
STATICFN UINT PASCAL NEAR
wAddDeviceNodeToList(
    LPMCI_DEVICE_NODE node
    )
{
    UINT wDeviceID = node->wDeviceID;
    LPMCI_DEVICE_NODE FAR *lpTempList;
    UINT iReallocSize;

    while (wDeviceID >= MCI_wDeviceListSize)
    {
        // The list is full so try to grow it
        iReallocSize = MCI_wDeviceListSize + 1 + MCI_DEVICE_LIST_GROW_SIZE;
        iReallocSize *= sizeof(LPMCI_DEVICE_NODE);
        if ((lpTempList = mciReAlloc(MCI_lpDeviceList, iReallocSize)) == NULL)
        {
            DOUT ("wReserveDeviceID:  cannot grow device list\r\n");
            return 0;
        }
        MCI_lpDeviceList = lpTempList;
        MCI_wDeviceListSize += MCI_DEVICE_LIST_GROW_SIZE;
    }

    if (wDeviceID >= MCI_wNextDeviceID) {
        MCI_wNextDeviceID = wDeviceID + 1;
    }

    MCI_lpDeviceList[wDeviceID] = node;

    return wDeviceID;
}

//
// Allocate space for the given string and assign the name to the given
// device.
// Return FALSE if could not allocate memory
//
STATICFN BOOL PASCAL NEAR
mciAddDeviceName(
    LPMCI_DEVICE_NODE nodeWorking,
    LPCSTR lpDeviceName
    )
{
    nodeWorking->lpstrName = mciAlloc(lstrlen(lpDeviceName)+1);

    if (nodeWorking->lpstrName == NULL)
    {
        DOUT ("mciAddDeviceName:  Out of memory allocating device name\r\n");
        return FALSE;
    }

    // copy device name to mci node and lowercase it

    lstrcpy(nodeWorking->lpstrName, lpDeviceName);
//!!    mciToLower(nodeWorking->lpstrName);

    return TRUE;
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciAllocateNode | Allocate a new driver entry
 *
 * @parm DWORD | dwFlags | As sent with MCI_OPEN message
 * @parm LPCSTR | lpDeviceName | The device name
 * @parm LPMCI_DEVICE_NODE FAR * | lpnodeNew | new node allocated
 *
 * @rdesc The device ID to the new node.  0 on error.
 *
 */
STATICFN UINT PASCAL NEAR mciAllocateNode(
    DWORD dwFlags,
    LPCSTR lpDeviceName,
    LPMCI_DEVICE_NODE FAR *lpnodeNew
    )
{
    LPMCI_DEVICE_NODE   nodeWorking;
    UINT wDeviceID;

    if ((nodeWorking = mciAlloc(sizeof(MCI_DEVICE_NODE))) == NULL)
    {
        DOUT("Out of memory in mciAllocateNode\r\n");
        return 0;
    }

    // The device ID is a global resource so we fetch it from 32-bit MCI.
    // A node is also allocated on the 32-bit side, and marked as 16-bit. The
    // node will be freed during mciFreeDevice, and acts as a place holder for
    // the device ID.

    wDeviceID = (UINT) mciMessage(THUNK_MCI_ALLOCATE_NODE,
                                  dwFlags,
                                  (DWORD)lpDeviceName,
                                  0L, 0L);

    // Copy the working node to the device list
    nodeWorking->wDeviceID = wDeviceID;
    if (wAddDeviceNodeToList(nodeWorking) == 0)
    {
        DOUT ("mciAllocateNode:  Cannot allocate new node\r\n");
        mciFree(nodeWorking);
        return 0;
    }

    // Initialize node
    nodeWorking->hCreatorTask = GetCurrentTask ();
    nodeWorking->dwMCIFlags |= MCINODE_16BIT_DRIVER;

    if (dwFlags & MCI_OPEN_ELEMENT_ID) {
        // No device name, just an element ID
        nodeWorking->dwElementID = (DWORD)lpDeviceName;
    }
    else {
        if (!mciAddDeviceName (nodeWorking, lpDeviceName))
        {
            mciFree (nodeWorking);
            return 0;
        }
    }
    *lpnodeNew = nodeWorking;

    return nodeWorking->wDeviceID;
}

//
// Reparse the original command parameters
// Returns MCIERR code.  If the reparse fails the original error code
// from the first parsing is returned.
//
STATICFN UINT PASCAL NEAR
mciReparseOpen(
    LPMCI_INTERNAL_OPEN_INFO lpOpenInfo,
    UINT wCustomTable,
    UINT wTypeTable,
    LPDWORD lpdwFlags,
    LPMCI_OPEN_PARMS FAR *lplpOpen,
    UINT wDeviceID
    )
{
    LPSTR               lpCommand;
    LPDWORD             lpdwParams;
    UINT                wErr;
    DWORD               dwOldFlags = *lpdwFlags;

// If the custom table contains no open command
    if (wCustomTable == -1 ||
        (lpCommand = FindCommandInTable (wCustomTable, szOpen, NULL)) == NULL)
    {
// Try the type specific table
        lpCommand = FindCommandInTable (wTypeTable, szOpen, NULL);
// If it still cannot be parsed
        if (lpCommand == NULL)
            return lpOpenInfo->wParsingError;
        wCustomTable = wTypeTable;
    }
// A new version of 'open' was found
// Free previous set of parameters
    mciParserFree (lpOpenInfo->lpstrPointerList);
    *lpdwFlags = 0;

    if ((lpdwParams =
            (LPDWORD)mciAlloc (sizeof(DWORD) * MCI_MAX_PARAM_SLOTS))
        == NULL)
            return MCIERR_OUT_OF_MEMORY;

    wErr = mciParseParams (lpOpenInfo->lpstrParams, lpCommand,
                            lpdwFlags,
                            (LPSTR)lpdwParams,
                            sizeof(DWORD) * MCI_MAX_PARAM_SLOTS,
                            &lpOpenInfo->lpstrPointerList, NULL);
// We don't need this around anymore
    mciUnlockCommandTable (wCustomTable);

// If there was a parsing error
    if (wErr != 0)
    {
// Make sure this does not get free'd by mciSendString
        lpOpenInfo->lpstrPointerList = NULL;

        mciFree (lpdwParams);
        return wErr;
    }
    if (dwOldFlags & MCI_OPEN_TYPE)
    {
// Device type was already extracted so add it manually
        ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrDeviceType
            = (*lplpOpen)->lpstrDeviceType;
        *lpdwFlags |= MCI_OPEN_TYPE;
    }
    if (dwOldFlags & MCI_OPEN_ELEMENT)
    {
// Element name was already extracted so add it manually
        ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrElementName
            = (*lplpOpen)->lpstrElementName;
        *lpdwFlags |= MCI_OPEN_ELEMENT;
    }
    if (dwOldFlags & MCI_OPEN_ALIAS)
    {
// Alias name was already extracted so add it manually
        ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrAlias
            = (*lplpOpen)->lpstrAlias;
        *lpdwFlags |= MCI_OPEN_ALIAS;
    }
    if (dwOldFlags & MCI_NOTIFY)
// Notify was already extracted so add it manually
        ((LPMCI_OPEN_PARMS)lpdwParams)->dwCallback
            = (*lplpOpen)->dwCallback;

    // Replace old parameter list with new list
    *lplpOpen = (LPMCI_OPEN_PARMS)lpdwParams;

    return 0;
}

// See if lpstrDriverName exists in the profile strings of the [mci]
// section and return the keyname in lpstrDevice and the
// profile string in lpstrProfString
// Returns 0 on success or an error code
STATICFN UINT PASCAL NEAR
mciFindDriverName(
    LPCSTR lpstrDriverName,
    LPSTR lpstrDevice,
    LPSTR lpstrProfString,
    UINT wProfLength
    )
{
    LPSTR lpstrEnum, lpstrEnumStart;
    UINT wEnumLen = 100;
    UINT wErr;
    LPSTR lpstrDriverTemp, lpstrProfTemp;

// Enumerate values, trying until they fit into the buffer
    while (TRUE) {
        if ((lpstrEnum = mciAlloc (wEnumLen)) == NULL)
            return MCIERR_OUT_OF_MEMORY;

        wErr = GetPrivateProfileString ((LPSTR)szMCISectionName,
                                    NULL, szNull, lpstrEnum, wEnumLen,
                                    szSystemIni);

        if (*lpstrEnum == '\0')
        {
            mciFree (lpstrEnum);
            return MCIERR_DEVICE_NOT_INSTALLED;
        }

        if (wErr == wEnumLen - 2)
        {
            wEnumLen *= 2;
            mciFree (lpstrEnum);
        } else
            break;
    }

    lpstrEnumStart = lpstrEnum;
    if (lstrlen(lpstrDriverName) >= MCI_MAX_DEVICE_TYPE_LENGTH) {
        wErr = MCIERR_DEVICE_LENGTH;
        goto exit_fn;
    }
    lstrcpy(lpstrDevice, lpstrDriverName);
//!!    mciToLower (lpstrDevice);

// Walk through each string
    while (TRUE) {
        wErr = GetPrivateProfileString ((LPSTR)szMCISectionName,
                                    lpstrEnum, szNull, lpstrProfString,
                                    wProfLength,
                                    szSystemIni);
        if (*lpstrProfString == '\0')
        {
            DOUT ("mciFindDriverName: cannot load valid keyname\r\n");
            wErr = MCIERR_CANNOT_LOAD_DRIVER;
            goto exit_fn;
        }
// See if driver pathname matches input
//!!        mciToLower (lpstrProfString);
        lpstrDriverTemp = lpstrDevice;
        lpstrProfTemp = lpstrProfString;
// Find end of file name
        while (*lpstrProfTemp != '\0' && *lpstrProfTemp != ' ')
            ++lpstrProfTemp;
// Find begining of simple file name
        --lpstrProfTemp;
        while (*lpstrProfTemp != '\\' && *lpstrProfTemp != '/' &&
               *lpstrProfTemp != ':')
            if (--lpstrProfTemp < lpstrProfString)
                break;
        ++lpstrProfTemp;
// Compare to input
        while (*lpstrDriverTemp != '\0')
            if (*lpstrDriverTemp++ != *lpstrProfTemp++ ||
                (UINT)(lpstrProfTemp - lpstrProfString) >= wProfLength)
            {
                --lpstrProfTemp;
                break;
            }
// If the input was contained in the profile string and followed by
// a space or a '.' the we've got it!
        if (*lpstrDriverTemp == '\0' &&
            (*lpstrProfTemp == ' ' || *lpstrProfTemp == '.'))
        {
            if (lstrlen (lpstrEnum) >= MCI_MAX_DEVICE_TYPE_LENGTH)
            {
                DOUT ("mciFindDriverName: device name too long\r\n");
                wErr = MCIERR_DEVICE_LENGTH;
                goto exit_fn;
            }
            lstrcpy (lpstrDevice, lpstrEnum);
            wErr = 0;
            goto exit_fn;
        }
// Skip to next keyname
        while (*lpstrEnum++ != '\0') {}
// Error if no more left
        if (*lpstrEnum == 0)
        {
            wErr = MCIERR_INVALID_DEVICE_NAME;
            goto exit_fn;
        }
    }

exit_fn:
    mciFree (lpstrEnumStart);
    return wErr;
}

//
// Identifies the driver name to load
// Loads the driver
// Reparses open command if necessary
// Sets a default break key
//
// lpOpenInfo contains various info for reparsing
//
// bDefaultAlias indicates that the alias need not be verified because
// it was internally assigned
//
STATICFN UINT PASCAL NEAR
mciLoadDevice(
    DWORD dwFlags,
    LPMCI_OPEN_PARMS lpOpen,
    LPMCI_INTERNAL_OPEN_INFO lpOpenInfo,
    BOOL bDefaultAlias
    )
{
    LPMCI_DEVICE_NODE   nodeWorking;
    HINSTANCE           hDriver;
    UINT                wID, wErr;
    char                strProfileString[MCI_PROFILE_STRING_LENGTH];
    MCI_OPEN_DRIVER_PARMS DriverOpen;
    HDRVR               hDrvDriver;
    LPSTR               lpstrParams;
    LPCSTR              lpstrInstallName, lpstrDeviceName;
    LPSTR               lpstrCopy = NULL;
    LPMCI_OPEN_PARMS    lpOriginalOpenParms = lpOpen;

/* Check for the device name in SYSTEM.INI */
    lpstrInstallName = lpOpen->lpstrDeviceType;
    wErr = GetPrivateProfileString ((LPSTR)szMCISectionName,
                                lpstrInstallName,
                                szNull, (LPSTR)strProfileString,
                                MCI_PROFILE_STRING_LENGTH,
                                szSystemIni);

// If device name not found
    if (wErr == 0)
    {
        int nLen = lstrlen (lpstrInstallName);
        int index;

// Try for the device name with a '1' thru a '9' appended to it

        if ((lpstrCopy = (LPSTR)mciAlloc (nLen + 2)) // space for digit too
            == NULL)
        {
            DOUT ("mciLoadDevice:  cannot allocate device name copy\r\n");
            return MCIERR_OUT_OF_MEMORY;
        }
        lstrcpy (lpstrCopy, lpstrInstallName);

        lpstrCopy[nLen + 1] = '\0';

        for (index = 1; index <= 9; ++index)
        {
            lpstrCopy[nLen] = (char)('0' + index);
            wErr = GetPrivateProfileString ((LPSTR)szMCISectionName,
                                        lpstrCopy,
                                        szNull, (LPSTR)strProfileString,
                                        MCI_PROFILE_STRING_LENGTH,
                                        szSystemIni);
            if (wErr != 0)
                break;
        }
        if (wErr == 0)
        {
            mciFree (lpstrCopy);
            if ((lpstrCopy = (LPSTR)mciAlloc (MCI_MAX_DEVICE_TYPE_LENGTH))
                == NULL)
            {
                DOUT ("mciLoadDevice:  cannot allocate device name copy\r\n");
                return MCIERR_OUT_OF_MEMORY;
            }
            if ((wErr = mciFindDriverName (lpstrInstallName, lpstrCopy,
                                           (LPSTR)strProfileString,
                                           MCI_PROFILE_STRING_LENGTH)) != 0)
                goto exit_fn;
        }
        lpstrInstallName = lpstrCopy;
    }

// Break out the device driver pathname and the parameter list

    lpstrParams = strProfileString;

// Eat blanks
    while (*lpstrParams != ' ' && *lpstrParams != '\0')
        ++lpstrParams;

// Terminate driver file name
    if (*lpstrParams == ' ') *lpstrParams++ = '\0';

//Now "strProfileString" is the device driver and "lpstrParams" is
//the parameter string
    if (dwFlags & (MCI_OPEN_ELEMENT | MCI_OPEN_ELEMENT_ID))
        lpstrDeviceName = lpOpen->lpstrElementName;
    else
        lpstrDeviceName = lpOpen->lpstrDeviceType;

    if (dwFlags & MCI_OPEN_ALIAS)
    {
// If the alias is default then we've already checked its uniqueness
        if (!bDefaultAlias &&
            mciGetDeviceIDInternalEx (lpOpen->lpstrAlias,
                                      lpOpenInfo->hCallingTask) != 0)
        {
            wErr = MCIERR_DUPLICATE_ALIAS;
            goto exit_fn;
        }
        lpstrDeviceName = lpOpen->lpstrAlias;
    }

    wID = mciAllocateNode (dwFlags, lpstrDeviceName, &nodeWorking);

    if (wID == 0)
    {
        wErr = MCIERR_CANNOT_LOAD_DRIVER;
        goto exit_fn;
    }

// Identify the task which initiated the open command
    nodeWorking->hOpeningTask = lpOpenInfo->hCallingTask;

// Initialize the driver
    DriverOpen.lpstrParams = lpstrParams;
    DriverOpen.wCustomCommandTable = -1;
    DriverOpen.wType = 0;
    DriverOpen.wDeviceID = wID;

// Load the driver
    hDrvDriver = OpenDriver ((LPSTR)strProfileString, szMCISectionName,
                          (LPARAM)(DWORD)(LPMCI_OPEN_DRIVER_PARMS)&DriverOpen);
    if (hDrvDriver == NULL)
    {
        DOUT ("mciLoadDevice:  OpenDriver failed\r\n");
// Assume driver has free'd any custom command table when it failed the open
        mciFreeDevice (nodeWorking);
        wErr = MCIERR_CANNOT_LOAD_DRIVER;
        goto exit_fn;
    }

    lpOpen->wDeviceID = wID;
    lpOpen->wReserved0 = 0;

    hDriver = GetDriverModuleHandle (hDrvDriver);

    nodeWorking->hDrvDriver = hDrvDriver;
    nodeWorking->hDriver = hDriver;

// Driver provides custom device table and type
    nodeWorking->wCustomCommandTable = DriverOpen.wCustomCommandTable;
    nodeWorking->wDeviceType = DriverOpen.wType;

// Load driver's type table
    if ((nodeWorking->wCommandTable = mciLoadTableType (DriverOpen.wType))
        == -1)
// Load from a file if necessary
        nodeWorking->wCommandTable =
            mciLoadCommandResource (ghInst, lpOpen->lpstrDeviceType,
                                    DriverOpen.wType);

// Record this for 'sysinfo installname'
    if ((nodeWorking->lpstrInstallName =
                    mciAlloc (lstrlen (lpstrInstallName) + 1))
        == NULL)
    {
        mciCloseDevice (wID, 0L, NULL, FALSE);
        wErr = MCIERR_OUT_OF_MEMORY;
        goto exit_fn;
    } else
        lstrcpy (nodeWorking->lpstrInstallName, lpstrInstallName);

// Reparse the input command if no type was known the first time or if
// there was a custom command table
// and there were any open command parameters
    if (lpOpenInfo->lpstrParams != NULL)
    {
        if ((wErr = mciReparseOpen (lpOpenInfo,
                                    nodeWorking->wCustomCommandTable,
                                    nodeWorking->wCommandTable,
                                    &dwFlags, &lpOpen, wID)) != 0)
        {
            mciCloseDevice (wID, 0L, NULL, FALSE);
            goto exit_fn;
        }
// If there is no custom command table but mciSendString had a parsing
// error then close the device and report the error now
    } else if (lpOpenInfo->wParsingError != 0)
    {
        mciCloseDevice (wID, 0L, NULL, FALSE);
        wErr = lpOpenInfo->wParsingError;
        goto exit_fn;
    }

/* Send MCI_OPEN_DRIVER command to device */
    wErr = LOWORD(mciSendCommand (wID, MCI_OPEN_DRIVER,
                                 dwFlags, (DWORD)lpOpen));

// If the OPEN failed then close the device (don't send a CLOSE though)
    if (wErr != 0)
        mciCloseDevice (wID, 0L, NULL, FALSE);
    else
// Set default break key
        mciSetBreakKey (nodeWorking->wDeviceID, VK_CANCEL, NULL);

// If we replaced the open parms here then free them
    if (lpOriginalOpenParms != lpOpen && lpOpen != NULL)
        mciFree (lpOpen);

exit_fn:
    if (lpstrCopy != NULL)
        mciFree (lpstrCopy);

    return wErr;
}

/*
 * @doc INTERNAL MCI
 * @func BOOL | mciExtractDeviceType | If the given device name ends with
 * a file extension (.???) then try to get a typename from the
 * [mci extensions] section of WIN.INI
 *
 * @parm LPCSTR | lpstrDeviceName | The name to get the type from
 *
 * @parm LPSTR | lpstrDeviceType | The device type, returned to caller.
 *
 * @parm UINT | wBufLen | The length of the output buffer
 *
 * @rdesc TRUE if the type was found, FALSE otherwise
 *
 */
BOOL PASCAL NEAR mciExtractDeviceType (
LPCSTR lpstrDeviceName,
LPSTR lpstrDeviceType,
UINT wBufLen)
{
    LPCSTR lpstrExt = lpstrDeviceName;
    int i;

// Goto end of string
    while (*lpstrExt != '\0')
    {
// '!' case is handled elsewhere
        if (*lpstrExt == '!')
            return FALSE;
        ++lpstrExt;
    }

// Must be at least 2 characters in string
    if (lpstrExt - lpstrDeviceName < 2)
        return FALSE;

    lpstrExt -= 1;

// Does not count if last character is '.'
    if (*lpstrExt == '.')
        return FALSE;

    lpstrExt -= 1;
// Now looking at second to the last character.  Check this and the two
// previous characters for a '.'

    for (i=1; i<=3; ++i)
    {
// Cannot have path separator here
        if (*lpstrExt == '/' || *lpstrExt == '\\')
            return FALSE;

        if (*lpstrExt == '.')
        {
            ++lpstrExt;
            if (GetProfileString (MCI_EXTENSIONS, lpstrExt, szNull,
                                            lpstrDeviceType, wBufLen) != 0)
                return TRUE;
        }
        if (lpstrExt == lpstrDeviceName)
            return FALSE;
        --lpstrExt;
    }
    return FALSE;
}

// Copy characters up to cSeparater into output which is allocated
// by this function using mciAlloc.  Return the input pointer pointing
// to the character after cSeparator
// unless the separator is '\0' in which case it points to the end.
//
// Return the allocated pointer
//
// If bMustFind then the output string is created only if the token
// is found and is otherwise NULL.  Else the output string is always created.
//
// cSeparator is ignored inside matching quotes ("abd"), the quotes
// are not coppied and doubled
// quotes inside are compressed to one.  There must be a terminating quote.
// Quotes are treated normally unless the first character is a quote
//
// Function return value is 0 or an MCIERR code.  A missing separator does
// not cause an error return.
UINT PASCAL NEAR mciEatToken (LPCSTR FAR *lplpstrInput, char cSeparater,
                  LPSTR FAR *lplpstrOutput, BOOL bMustFind)
{
    LPCSTR lpstrEnd = *lplpstrInput, lpstrCounter;
    LPSTR  lpstrOutput;
    UINT wLen;
    BOOL bInQuotes = FALSE, bParseQuotes = TRUE, bQuoted = FALSE;

// Clear output
   *lplpstrOutput = NULL;

// Scan for token or end of string
    while ((*lpstrEnd != cSeparater || bInQuotes) && *lpstrEnd != '\0')
    {
// If quote
        if (*lpstrEnd == '"' && bParseQuotes)
        {
// If inside quotes
            if (bInQuotes)
            {
// If next character is a quote also
                if (*(lpstrEnd + 1) == '"')
// Skip it
                    ++lpstrEnd;
                else
                    bInQuotes = FALSE;
            } else
            {
                bInQuotes = TRUE;
                bQuoted = TRUE;
            }
        } else if (!bInQuotes)
        {
            if (bQuoted)
                return MCIERR_EXTRA_CHARACTERS;
// A non-quote was read first so treat any quotes as normal characters
            bParseQuotes = FALSE;
        }
        ++lpstrEnd;
    }

    if (bInQuotes)
        return MCIERR_NO_CLOSING_QUOTE;

// Fail if the token was not found and bMustFind is TRUE
    if (*lpstrEnd != cSeparater && bMustFind)
        return 0;

// Length of new string (INCLUDES QUOTES NOT COPIED)
    wLen = lpstrEnd - *lplpstrInput + 1;

    if ((*lplpstrOutput = mciAlloc (wLen)) == NULL)
        return MCIERR_OUT_OF_MEMORY;

// Copy into allocated space
    lpstrCounter = *lplpstrInput;
    lpstrOutput = *lplpstrOutput;
    bInQuotes = FALSE;

    while (lpstrCounter != lpstrEnd)
    {
        if (*lpstrCounter == '"' && bParseQuotes)
        {
            if (bInQuotes)
            {
// If this is a doubled quote
                if (*(lpstrCounter + 1) == '"')
// Copy it
                    *lpstrOutput++ = *lpstrCounter++;
                else
                    bInQuotes = FALSE;
            } else
                bInQuotes = TRUE;
// Skip the quote
            ++lpstrCounter;
        } else
            *lpstrOutput++ = *lpstrCounter++;
    }

    *lpstrOutput = '\0';
    if (*lpstrEnd == '\0')
        *lplpstrInput = lpstrEnd;
    else
        *lplpstrInput = lpstrEnd + 1;

    return 0;
}

// Take the type number from the open parameters and return
// it as a string in lplpstrType which must be free'd with mciFree
// Returns 0 or an MCI error code
UINT PASCAL NEAR mciExtractTypeFromID (
LPMCI_OPEN_PARMS lpOpen)
{
    int nSize;
    LPSTR lpstrType;

    if ((lpstrType = mciAlloc (MCI_MAX_DEVICE_TYPE_LENGTH)) == NULL)
        return MCIERR_OUT_OF_MEMORY;

// Load the type string corresponding to the ID
    if ((nSize = LoadString (ghInst,
                                LOWORD ((DWORD)lpOpen->lpstrDeviceType),
                                lpstrType, MCI_MAX_DEVICE_TYPE_LENGTH)) == 0)
        return MCIERR_EXTENSION_NOT_FOUND;

// Add ordinal (if any) onto the end of the device type name
    if (HIWORD (lpOpen->lpstrDeviceType) != 0)
    {
        if (nSize > MCI_MAX_DEVICE_TYPE_LENGTH - 11)
        {
            DOUT ("mciExtractTypeFromID:  type + ordinal too long\r\n");
            return MCIERR_DEVICE_ORD_LENGTH;
        }

        wsprintf (lpstrType + nSize, szUnsignedFormat,
                    HIWORD ((DWORD)lpOpen->lpstrDeviceType));
    }
    lpOpen->lpstrDeviceType = lpstrType;
    return 0;
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciOpenDevice | Open an MCI device for access.
 * Used in processing the MCI_OPEN message.
 *
 * @parm DWORD | dwFlags | Open Flags
 * @parm LPMCI_OPEN_PARMS | lpOpen | Description of device
 *
 * @rdesc 0 if successful or an error code
 * @flag MCIERR_INVALID_DEVICE_NAME | Name not known
 * @flag MCIERR_DEVICE_OPEN | Device is already open and is not sharable
 *
 * @comm This function does the following:
 * 1) Check to see if device is already open.  If so, return an error
 *
 * 2) Locate the device name in the SYSTEM.INI file and load
 *    the corresponding device driver DLL
 *
 * 3) Allocate and initialize a new device description block
 *
 */
UINT NEAR PASCAL mciOpenDevice (
DWORD dwStartingFlags,
LPMCI_OPEN_PARMS lpOpen,
LPMCI_INTERNAL_OPEN_INFO lpOpenInfo)
{
    LPSTR               lpstrNewType = NULL;
    UINT                wID, wReturn;
    LPCSTR              lpstrDeviceName;
    LPSTR               lpstrNewElement = NULL;
    BOOL                bFromTypeID = FALSE;
    LPCSTR               lpstrOriginalType;
    LPCSTR               lpstrOriginalElement;
    LPCSTR               lpstrOriginalAlias;
    DWORD               dwFlags = dwStartingFlags;
    BOOL                bDefaultAlias = FALSE;

// Initialize
    if (lpOpen == NULL)
        return MCIERR_NULL_PARAMETER_BLOCK;
    lpstrOriginalType = lpOpen->lpstrDeviceType;
    lpstrOriginalElement = lpOpen->lpstrElementName;
    lpstrOriginalAlias = lpOpen->lpstrAlias;

// The type number is given explicitly, convert it to a type name
    if (dwFlags & MCI_OPEN_TYPE_ID)
        if ((wReturn = mciExtractTypeFromID (lpOpen)) != 0)
            return wReturn;
        else
            bFromTypeID = TRUE;

// The device name is the device type of a simple device or the device
// element of a compound device

    if (dwFlags & MCI_OPEN_ELEMENT)
        lpstrDeviceName = lpstrOriginalElement;
    else if (dwFlags & MCI_OPEN_TYPE)
        lpstrDeviceName = lpOpen->lpstrDeviceType;
    else
        return MCIERR_MISSING_PARAMETER;

    if (lpstrDeviceName == NULL)
    {
        DOUT ("mciOpenDevice:  Device name is NULL\r\n");
        return MCIERR_INVALID_DEVICE_NAME;
    }

// Is the device already open?
    if (dwFlags & MCI_OPEN_ELEMENT_ID)
        wID = mciGetDeviceIDFromElementID ((DWORD)lpstrDeviceName,
                                           lpOpen->lpstrDeviceType);
    else 
        wID = mciGetDeviceIDInternalEx ((dwFlags & MCI_OPEN_ALIAS ?
                                       lpOpen->lpstrAlias : lpstrDeviceName),
                                       lpOpenInfo->hCallingTask);

// If the device is open already then return an error
    if (wID != 0)
        return dwFlags & MCI_OPEN_ALIAS ? MCIERR_DUPLICATE_ALIAS :
                                          MCIERR_DEVICE_OPEN;

// The device is not already open in that task by the name

// If the type was derived then skip all this crap
    if (bFromTypeID)
        goto load_device;

// If an element name is given but no type name (only via mciSendCommand)
    if (dwFlags & MCI_OPEN_ELEMENT && !(dwFlags & MCI_OPEN_TYPE))
    {
        lpstrNewType = mciAlloc (MCI_MAX_DEVICE_TYPE_LENGTH);
        if (lpstrNewType == NULL)
            return MCIERR_OUT_OF_MEMORY;

// Try to get the device type from the element name via a file extension
        if (mciExtractDeviceType (lpstrOriginalElement,
                                    lpstrNewType, MCI_MAX_DEVICE_TYPE_LENGTH))
        {
            lpOpen->lpstrDeviceType = lpstrNewType;
            dwFlags |= MCI_OPEN_TYPE;
        } else
        {
            mciFree (lpstrNewType);
            return MCIERR_EXTENSION_NOT_FOUND;
        }
    } else if (dwFlags & MCI_OPEN_TYPE && !(dwFlags & MCI_OPEN_ELEMENT))
// A type name is given but no element
    {
// Try to extract a device type from the given device name via a file extension
        lpstrNewType = mciAlloc (MCI_MAX_DEVICE_TYPE_LENGTH);
        if (lpstrNewType == NULL)
            return MCIERR_OUT_OF_MEMORY;
        if (mciExtractDeviceType (lpOpen->lpstrDeviceType, lpstrNewType,
                                    MCI_MAX_DEVICE_TYPE_LENGTH))
        {
// Fix up the type and element names
            dwFlags |= MCI_OPEN_ELEMENT;
            lpOpen->lpstrElementName = lpOpen->lpstrDeviceType;
            lpOpen->lpstrDeviceType = lpstrNewType;
        } else
// Failed to extract type so...
// Try to get a compound element name ('!' separator)
        {
            LPCSTR lpstrTemp = lpOpen->lpstrDeviceType;

            mciFree (lpstrNewType);
            lpstrNewType = NULL;

            if ((wReturn = mciEatToken (&lpstrTemp, '!', &lpstrNewType, TRUE))
                != 0)
                goto cleanup;
            else if (lpstrNewType != NULL)
            {
                if ((wReturn = mciEatToken (&lpstrTemp, '\0',
                                            &lpstrNewElement, TRUE))
                    != 0)
                    goto cleanup;
                else if (lpstrNewElement != NULL &&
                           *lpstrNewElement != '\0')
                {
// See if this element name is in use
                    if (!(dwFlags & MCI_OPEN_ALIAS))
                        if (mciGetDeviceIDInternalEx (lpstrNewElement,
                                                      lpOpenInfo->hCallingTask))
                        {
                            wReturn = MCIERR_DEVICE_OPEN;
                            goto cleanup;
                        }
// Swap type and element for new ones
                    lpOpen->lpstrElementName = lpstrNewElement;
                    lpOpen->lpstrDeviceType = lpstrNewType;
                    dwFlags |= MCI_OPEN_ELEMENT;
                }
            }
        }
    } else
        lpstrNewType = NULL;

// Tack on a default alias if none is given
    if (! (dwFlags & MCI_OPEN_ALIAS))
    {
        LPCSTR lpstrAlias;

// If an element name exists then the alias is the element name
        if (dwFlags & MCI_OPEN_ELEMENT)
        {
// If a device ID was specified then there is no alias
            if (dwFlags & MCI_OPEN_ELEMENT_ID)
                lpstrAlias = NULL;
            else
                lpstrAlias = lpOpen->lpstrElementName;
// Otherwise the alias is the device type
        } else
            lpstrAlias = lpOpen->lpstrDeviceType;

        if (lpstrAlias != NULL)
        {
            lpOpen->lpstrAlias = lpstrAlias;
            dwFlags |= MCI_OPEN_ALIAS;
            bDefaultAlias = TRUE;
        }
    }

load_device:;
    wReturn = mciLoadDevice (dwFlags, lpOpen, lpOpenInfo, bDefaultAlias);

cleanup:
    if (lpstrNewElement != NULL)
        mciFree (lpstrNewElement);
    if (lpstrNewType != NULL)
        mciFree (lpstrNewType);
    if (bFromTypeID)
        mciFree ((LPSTR)lpOpen->lpstrDeviceType);

// Replace original items
    lpOpen->lpstrDeviceType = lpstrOriginalType;
    lpOpen->lpstrElementName = lpstrOriginalElement;
    lpOpen->lpstrAlias = lpstrOriginalAlias;

    return wReturn;
}

STATICFN void PASCAL NEAR
mciFreeDevice(
    LPMCI_DEVICE_NODE nodeWorking
    )
{
    UINT wID = nodeWorking->wDeviceID;

    mciMessage(THUNK_MCI_FREE_NODE, (DWORD) nodeWorking->wDeviceID, 0L, 0L, 0L);

    if (nodeWorking->lpstrName != NULL)
        mciFree (nodeWorking->lpstrName);

    if (nodeWorking->lpstrInstallName != NULL)
        mciFree (nodeWorking->lpstrInstallName);

    mciFree(MCI_lpDeviceList[wID]);
    MCI_lpDeviceList[wID] = NULL;

/* If this was the last device in the list, decrement next ID value */
    if (wID + 1 == MCI_wNextDeviceID) {
        --MCI_wNextDeviceID;
    }
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciCloseDevice | Close an MCI device.  Used in
 * processing the MCI_CLOSE message.
 *
 * @parm UINT | wID | The ID of the device to close
 * @parm DWORD | dwFlags | Close Flags
 * @parm LPMCI_GENERIC_PARMS | lpClose | Generic parameters
 * @parm BOOL | bCloseDriver | TRUE if the CLOSE command should be sent
 * on to the driver.
 *
 * @rdesc 0 if successful or an error code
 *
 * @comm This function sends an MCI_CLOSE_DRIVER message to the corresponding
 * driver if the use count is zero and then unloads the driver DLL
 *
 */
UINT NEAR PASCAL mciCloseDevice (
UINT wID,
DWORD dwFlags,
LPMCI_GENERIC_PARMS lpGeneric,
BOOL bCloseDriver)
{
    LPMCI_DEVICE_NODE nodeWorking;
    UINT wErr, wTable;

    nodeWorking = MCI_lpDeviceList[wID];

    if (nodeWorking == NULL)
    {
        DOUT ("mciCloseDevice:  NULL node from device ID--error if not auto-close\r\n");
        return 0;
    }

// If a close is in progress (usually this message comes from a Yield
// after a mciDriverNotify actuated by the active close) then exit
    if (nodeWorking->dwMCIFlags & MCINODE_ISCLOSING)
        return 0;

    nodeWorking->dwMCIFlags |= MCINODE_ISCLOSING;
    if (bCloseDriver)
    {
        MCI_GENERIC_PARMS   GenericParms;
// Make fake generic params if close came internally
        if (lpGeneric == NULL)
            lpGeneric = &GenericParms;

        wErr = LOWORD(mciSendCommand (wID, MCI_CLOSE_DRIVER, dwFlags,
                                            (DWORD)lpGeneric));
    }
    else
        wErr = 0;

// Must zero this to allow the table to be freed by the driver
    nodeWorking->wCustomCommandTable = 0;

    wTable = nodeWorking->wCommandTable;
// Must zero this to allow the table to be freed
    nodeWorking->wCommandTable = 0;
    mciFreeCommandResource (wTable);

    CloseDriver (nodeWorking->hDrvDriver, 0L, 0L);

    mciFreeDevice (nodeWorking);

    return wErr;
}

/*
 * @doc INTERNAL MCI DDK
 * @api DWORD | mciGetDriverData | Returns a pointer to the instance
 * data associated with an MCI device
 *
 * @parm UINT | wDeviceID | The MCI device ID
 *
 * @rdesc The driver instance data.  On error, returns 0 but since
 * the driver data might be zero, this cannot be verified by the caller
 * unless the instance data is known to be non-zero (e.g. a pointer)
 *
 */
DWORD WINAPI mciGetDriverData (
UINT wDeviceID)
{
    if (!MCI_VALID_DEVICE_ID(wDeviceID))
    {
        DOUT ("mciGetDriverData:  invalid device ID\r\n");
        return 0;
    }
    return MCI_lpDeviceList[wDeviceID]->lpDriverData;
}

/*
 * @doc INTERNAL MCI DDK
 * @func BOOL | mciSetDriverData | Sets the instance
 * data associated with an MCI device
 *
 * @parm UINT | wDeviceID | The MCI device ID
 *
 * @parm DWORD | dwData | Driver data to set
 *
 * @rdesc FALSE if the device ID is not known or there is insufficient
 * memory to load the device description, else TRUE.
 *
 */
BOOL WINAPI mciSetDriverData (
UINT wDeviceID,
DWORD dwData)
{
    if (!MCI_VALID_DEVICE_ID(wDeviceID))
    {
        DOUT ("mciSetDriverData:  invalid device ID\r\n");
        return FALSE;
    }
    MCI_lpDeviceList[wDeviceID]->lpDriverData = dwData;
    return TRUE;
}

/*
 * @doc INTERNAL MCI DDK
 * @api UINT | mciDriverYield | Used in a driver's idle loop
 * to yield to Windows
 *
 * @parm UINT | wDeviceID | Device ID that is yielding.
 *
 * @rdesc Non-zero if the driver should abort the operation.
 *
 */
UINT WINAPI mciDriverYield (
UINT wDeviceID)
{
    if (MCI_VALID_DEVICE_ID(wDeviceID))
    {
        LPMCI_DEVICE_NODE node = MCI_lpDeviceList[wDeviceID];

        if (node->fpYieldProc != NULL)
            return (node->fpYieldProc)(wDeviceID, node->dwYieldData);
    }

    Yield();
    return 0;
}

/*
 * @doc EXTERNAL MCI
 * @api BOOL | mciSetYieldProc | This function sets the address
 * of a callback procedure to be called periodically when an MCI device
 * is completing a command specified with the WAIT flag.
 *
 * @parm UINT | wDeviceID | Specifies the device ID of the MCI device to
 * which the yield procedure is to be assigned.
 *
 * @parm YIELDPROC | fpYieldProc | Specifies the callback procedure
 * to be called when the given device is yielding. Specify a NULL value
 * to disable any existing yield procedure.
 *
 * @parm DWORD | dwYieldData | Specifies the data sent to the yield procedure
 * when it is called for the given device.
 *
 * @rdesc Returns TRUE if successful. Returns FALSE for an invalid device ID.
 *
 * @cb int CALLBACK | YieldProc | <f YieldProc> is a placeholder for
 * the application-supplied function name. Export the actual name
 * by including it in the EXPORTS statement in your module-definition
 * file.
 *
 * @parm UINT | wDeviceID | Specifies the device ID of the MCI device.
 *
 * @parm DWORD | dwData | Specifies the application-supplied yield data
 * originally supplied in the <p dwYieldData> parameter.
 *
 * @rdesc Return zero to continue the operation. To cancel the operation,
 * return a nonzero value.
 *
 * @comm This call overrides any previous yield procedure for this device.
 *
 */
BOOL WINAPI mciSetYieldProc (
UINT wDeviceID,
YIELDPROC fpYieldProc,
DWORD dwYieldData)
{
    V_CALLBACK((FARPROC)fpYieldProc, FALSE);

    if (Is16bitDrv(wDeviceID)) {

        LPMCI_DEVICE_NODE node = MCI_lpDeviceList[wDeviceID];

        node->fpYieldProc = fpYieldProc;
        node->dwYieldData = dwYieldData;
        return TRUE;
    }

    return (BOOL)mciMessage( THUNK_MCI_SETYIELDPROC, (DWORD)wDeviceID,
                             (DWORD)fpYieldProc, dwYieldData, 0L );

}

/*
 * @doc EXTERNAL MCI
 * @api YIELDPROC | mciGetYieldProc | This function gets the address
 * of the callback procedure to be called periodically when an MCI device
 * is completing a command specified with the WAIT flag.
 *
 * @parm UINT | wDeviceID | Specifies the device ID of the MCI device to
 * which the yield procedure is to be retrieved from.
 *
 * @parm LPDWORD | lpdwYieldData | Optionally specifies a buffer to place
 * the yield data passed to the function in.  If the parameter is NULL, it
 * is ignored.
 *
 * @rdesc Returns the current yield proc if any, else returns NULL for an
 * invalid device ID.
 *
 */
YIELDPROC WINAPI mciGetYieldProc (
UINT wDeviceID,
LPDWORD lpdwYieldData)
{
    /*
    ** Is this a 16 bit device ID ?
    */
    if (Is16bitDrv(wDeviceID)) {

        if (lpdwYieldData != NULL) {
            V_WPOINTER(lpdwYieldData, sizeof(DWORD), NULL);
            *lpdwYieldData = MCI_lpDeviceList[wDeviceID]->dwYieldData;
        }
        return MCI_lpDeviceList[wDeviceID]->fpYieldProc;
    }

    /*
    ** No, so pass it on to the 32 bit code.
    */
    return (YIELDPROC)mciMessage( THUNK_MCI_GETYIELDPROC, (DWORD)wDeviceID,
                                  (DWORD)lpdwYieldData, 0L, 0L );
}

/*
 * @doc INTERNAL MCI
 * @api int | mciBreakKeyYieldProc | Procedure called to check a
 * key state for the given device
 *
 * @parm UINT | wDeviceID | Device ID which is yielding
 *
 * @parm DWORD | dwYieldData | Data for this device's yield proc
 *
 * @rdesc Non-zero if the driver should abort the operation. Currently
 * always returns 0.
 *
 */
int CALLBACK mciBreakKeyYieldProc (
UINT wDeviceID,
DWORD dwYieldData)
{
    HWND hwndCheck;
    int nState;

    hwndCheck = (HWND)HIWORD (dwYieldData);
    if (hwndCheck == NULL || hwndCheck == GetActiveWindow())
    {
        nState = GetAsyncKeyState (LOWORD(dwYieldData));

// Break if key is down or has been down
        if (nState & 1)
        {
            MSG msg;

            while (PeekMessage (&msg, hwndCheck, WM_KEYFIRST, WM_KEYLAST,
                   PM_REMOVE));
            return -1;
        }
    }
    Yield();
    return 0;
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciSetBreakKey | Set a key which will break a wait loop
 * for a given driver
 *
 * @parm UINT | wDeviceID | The device ID to assign a break key to
 *
 * @parm int | nVirtKey | Virtual key code to trap
 *
 * @parm HWND | hwndTrap | The handle to a window that must be active
 * for the key to be trapped.  If NULL then all windows will be checked
 *
 * @rdesc TRUE if successful, FALSE if invalid device ID
 *
 */
UINT PASCAL NEAR mciSetBreakKey (
UINT wDeviceID,
int nVirtKey,
HWND hwndTrap)
{
    return mciSetYieldProc (wDeviceID, mciBreakKeyYieldProc,
                         MAKELONG (nVirtKey, hwndTrap));
}
