/******************************************************************************
* Module Name: mci.c
*
* Media Control Architecture Driver Interface
*
* Contents:  MCI external message API's mciSendString and mciSendCommand
* Author:  DLL (DavidLe)
* Created: 2/13/90
*
* Copyright (c) 1990 Microsoft Corporation
*
\******************************************************************************/
#ifdef DEBUG
#ifndef DEBUG_RETAIL
#define DEBUG_RETAIL
#endif
#endif

#include <windows.h>
#include <string.h>

#define MMNOSEQ
#define MMNOJOY
#define MMNOWAVE
#define MMNOMIDI
#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


/* -------------------------------------------------------------------------
** Thunking stuff
** -------------------------------------------------------------------------
*/
LPMCIMESSAGE PASCAL mci32Message;
DWORD WINAPI mciSendCommand16(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwParam1,
    DWORD dwParam2
    );


//
//  Define the init code for this file.
//
#pragma alloc_text( INIT, MCITerminate )

#ifdef DEBUG_RETAIL
int DebugmciSendCommand;
#endif

#ifdef DEBUG
void PASCAL NEAR mciCheckLocks(void);
#endif

STATICFN UINT PASCAL NEAR
mciConvertReturnValue(
    UINT wType,
    UINT wErr,
    UINT wDeviceID,
    LPDWORD dwParams,
    LPSTR lpstrReturnString,
    UINT wReturnLength
    );

STATICFN DWORD NEAR PASCAL
mciSendStringInternal(
    LPCSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength,
    HWND hwndCallback,
    LPMCI_SYSTEM_MESSAGE lpMessage
    );

STATICFN DWORD NEAR PASCAL
mciSendSystemString(
    LPCSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength
    );

extern int FAR PASCAL
mciBreakKeyYieldProc(
    UINT wDeviceID,
    DWORD dwYieldData
    );


// From dosa.asm
extern int FAR PASCAL DosChangeDir(LPCSTR lpszPath);
extern WORD FAR PASCAL DosGetCurrentDrive(void);
extern BOOL FAR PASCAL DosSetCurrentDrive(WORD wDrive);
extern WORD FAR PASCAL DosGetCurrentDir(WORD wCurdrive, LPSTR lpszBuf);

#define MAX_PATHNAME 144

// This macro defines the list of messages for which mciSendString
// will not try to auto-open
#define MCI_CANNOT_AUTO_OPEN(wMessage) \
    (wMessage == MCI_OPEN || wMessage == MCI_SYSINFO \
        || wMessage == MCI_SOUND || wMessage == MCI_CLOSE \
        || wMessage == MCI_BREAK)

// This macro devices the list of message which do not require an open
// device.  It is a subset of MCI_CANNOT_AUTO_OPEN
#define MCI_DO_NOT_NEED_OPEN(wMessage) \
    (wMessage == MCI_OPEN || wMessage == MCI_SOUND || wMessage == MCI_SYSINFO)

// Strings used in mciAutoOpenDevice
          SZCODE szOpen[] = "open";
static    SZCODE szClose[] = "close";
static    SZCODE szNotify[] = "notify";
static    SZCODE szWait[] = "wait";
static    SZCODE szCmdFormat[] = "%ls %ls";
static    SZCODE szLongFormat[] = "%ld";
static    SZCODE szRectFormat[] = "%d %d %d %d";
extern char far szSystemDefault[];

// Special device name
static    SZCODE szNew[] = "new";

/******************************Public*Routine******************************\
* mciAppExit
*
* Notify the 32 bit code that a 16 bit app has died.
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
DWORD
mciAppExit(
    HTASK hTask
    )
{
    return mciMessage( THUNK_APP_EXIT, (DWORD)hTask,
                       0L, 0L, 0L );
}



/*****************************************************************************
 * @doc INTERNAL
 *
 * @api void | MciNotify  | called by mmWndProc when it recives a
 *                          MM_MCINOTIFY message
 * @rdesc None.
 *
 ****************************************************************************/

void FAR PASCAL
MciNotify(
    WPARAM wParam,
    LPARAM lParam
    )
{
    //
    //  wParam is the notify status
    //  lParam is the MCI device id
    //
    if (MCI_VALID_DEVICE_ID(LOWORD(lParam)) &&
        !(MCI_lpDeviceList[LOWORD(lParam)]->dwMCIFlags & MCINODE_ISCLOSING)) {
        MCI_lpDeviceList[LOWORD(lParam)]->dwMCIFlags |= MCINODE_ISAUTOCLOSING;
        mciCloseDevice (LOWORD(lParam), 0L, NULL, TRUE);
    }
}



STATICFN void NEAR PASCAL
HandleNotify(
    UINT wErr,
    UINT wDeviceID,
    DWORD dwFlags,
    DWORD dwParam2
    )
{
    LPMCI_GENERIC_PARMS lpGeneric = (LPMCI_GENERIC_PARMS)dwParam2;
    HWND hwndCallback;
    if (wErr == 0 && dwFlags & MCI_NOTIFY && lpGeneric != NULL &&
        (hwndCallback = (HWND)(UINT)lpGeneric->dwCallback) != NULL)

        mciDriverNotify (hwndCallback, wDeviceID, MCI_NOTIFY_SUCCESSFUL);
}

#ifdef DEBUG_RETAIL
//
// Dump the string form of an MCI command
//
UINT PASCAL NEAR
mciDebugOut(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwFlags,
    DWORD dwParam2,
    LPMCI_DEVICE_NODE nodeWorking
    )
{
    LPSTR lpCommand, lpFirstParameter, lpPrevious, lszDebugOut;
    char strTemp[256];
    UINT wID, wOffset, wOffsetFirstParameter, wReturnType;
    DWORD dwValue;
    DWORD dwMask;
    UINT wTable;

// Find the command table for the given command message ID
    lpCommand = FindCommandItem( wDeviceID, NULL,
                                 (LPSTR)MAKELONG (wMessage, 0),
                                 NULL, &wTable);

    if (lpCommand == NULL)
    {
        if (wMessage != MCI_OPEN_DRIVER && wMessage != MCI_CLOSE_DRIVER)
            ROUT ("MMSYSTEM: mciDebugOut:  Command table not found");
        return 0;
    }

    lszDebugOut = mciAlloc(512);
    if (!lszDebugOut) {
        ROUT("MMSYSTEM: Not enough memory to display command");
	return 0;
    }

//  Dump the command name
    wsprintf(lszDebugOut, "MMSYSTEM: MCI command: \"%ls", lpCommand);

// Dump the device name
    if (wDeviceID == MCI_ALL_DEVICE_ID)
    {
        lstrcat(lszDebugOut, " all");
    }
    else if (nodeWorking != NULL)
    {
        if (nodeWorking->dwMCIOpenFlags & MCI_OPEN_ELEMENT_ID)
        {
            wsprintf(lszDebugOut + lstrlen(lszDebugOut), " Element ID:0x%lx", nodeWorking->dwElementID);
        } else if (nodeWorking->lpstrName != NULL)
        {
            wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", nodeWorking->lpstrName);
        }
    }

// Skip past command entry
    lpCommand += mciEatCommandEntry (lpCommand, NULL, NULL);

// Get the next entry
    lpFirstParameter = lpCommand;

// Skip past the DWORD return value
    wOffsetFirstParameter = 4;

    lpCommand += mciEatCommandEntry (lpCommand, &dwValue, &wID);

// If it is a return value, skip it
    if (wID == MCI_RETURN)
    {
        wReturnType = (UINT)dwValue;
        lpFirstParameter = lpCommand;
        wOffsetFirstParameter += mciGetParamSize (dwValue, wID);
        lpCommand += mciEatCommandEntry (lpCommand, &dwValue, &wID);
    }
    else {
        wReturnType = (UINT)0;
    }

// Dump device name parameter to OPEN
    if (wMessage == MCI_OPEN)
    {
        LPCSTR lpstrDeviceType =
            ((LPMCI_OPEN_PARMS)dwParam2)->lpstrDeviceType;
        LPCSTR lpstrElementName =
            ((LPMCI_OPEN_PARMS)dwParam2)->lpstrElementName;

// Tack on device type
        if (dwFlags & MCI_OPEN_TYPE_ID)
        {
            LPMCI_OPEN_PARMS lpOpen = (LPMCI_OPEN_PARMS)dwParam2;
            DWORD dwOld = (DWORD)lpOpen->lpstrDeviceType;
            if (mciExtractTypeFromID ((LPMCI_OPEN_PARMS)dwParam2) != 0)
                strTemp[0] = '\0';
            lstrcpy (strTemp, lpOpen->lpstrDeviceType);
            mciFree ((LPSTR)lpOpen->lpstrDeviceType);
            lpOpen->lpstrDeviceType = (LPSTR)dwOld;
        } else if (lpstrDeviceType != NULL)
            lstrcpy (strTemp, lpstrDeviceType);
        else
            strTemp[0] = '\0';

        if (dwFlags & MCI_OPEN_ELEMENT_ID)
        {
// Tack on element ID
            lstrcat (strTemp, " Element ID:");
            wsprintf (strTemp + lstrlen (strTemp), szLongFormat,
                      LOWORD ((DWORD)lpstrDeviceType));
        } else
        {
// Add separator if both type name and element name are present
            if (lpstrDeviceType != 0 && lpstrElementName != 0)
                lstrcat (strTemp, "!");
            if (lpstrElementName != 0 && dwFlags & MCI_OPEN_ELEMENT)
                lstrcat (strTemp, lpstrElementName);
        }
        wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", (LPSTR)strTemp);
    }


// Walk through each flag
    for (dwMask = 1; dwMask;)
    {
// Is this bit set?
        if ((dwFlags & dwMask) != 0 && !
// The MCI_OPEN_TYPE and MCI_OPEN_ELEMENT flags are taken care of
// above
            (wMessage == MCI_OPEN && (dwMask == MCI_OPEN_TYPE
                                      || dwMask == MCI_OPEN_ELEMENT)))
        {
            lpPrevious = lpCommand = lpFirstParameter;
            wOffset = 0;
            lpCommand += mciEatCommandEntry (lpCommand, &dwValue, &wID);

// What parameter uses this bit?
            while (wID != MCI_END_COMMAND && dwValue != dwMask)
            {
                wOffset += mciGetParamSize (dwValue, wID);

                if (wID == MCI_CONSTANT)
                    while (wID != MCI_END_CONSTANT)
                        lpCommand += mciEatCommandEntry (lpCommand,
                                                         NULL, &wID);

                lpPrevious = lpCommand;
                lpCommand += mciEatCommandEntry (lpCommand, &dwValue, &wID);
            }

            if (wID != MCI_END_COMMAND)
            {
// Found the parameter which matches this flag bit
// Print the parameter name
                if (*lpPrevious)
                    wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", lpPrevious);

// Print any argument
                switch (wID)
                {
                    case MCI_STRING:
                        wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", *(LPSTR FAR *)((LPSTR)dwParam2 + wOffset + wOffsetFirstParameter));
                        break;
                    case MCI_CONSTANT:
                    {
                        DWORD dwConst = *(LPDWORD)((LPSTR)dwParam2 + wOffset +
                                             wOffsetFirstParameter);
                        UINT wLen;
                        BOOL bFound;

                        for (bFound = FALSE; wID != MCI_END_CONSTANT;)
                        {
                            wLen = mciEatCommandEntry (lpCommand,
                                                       &dwValue, &wID);

                            if (dwValue == dwConst)
                            {
                                bFound = TRUE;
                                wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", lpCommand);
                            }

                            lpCommand += wLen;
                        }
                        if (bFound)
                            break;
// FALL THROUGH
                    }
                    case MCI_INTEGER:
                        wsprintf ((LPSTR)strTemp, szLongFormat,
                                  *(LPDWORD)((LPSTR)dwParam2 + wOffset +
                                             wOffsetFirstParameter));
                        wsprintf(lszDebugOut + lstrlen(lszDebugOut), " %ls", (LPSTR)strTemp);
                        break;
                }
            }
        }
// Go the the next flag
        dwMask <<= 1;
    }
    mciUnlockCommandTable (wTable);
    lstrcat(lszDebugOut, "\"");
    ROUTS(lszDebugOut);
    mciFree(lszDebugOut);
    return wReturnType;
}
#endif

STATICFN DWORD PASCAL NEAR
mciBreak(
    UINT wDeviceID,
    DWORD dwFlags,
    LPMCI_BREAK_PARMS lpBreakon
    )
{
    HWND hwnd;

    if (dwFlags & MCI_BREAK_KEY)
    {
        if (dwFlags & MCI_BREAK_OFF)
            return MCIERR_FLAGS_NOT_COMPATIBLE;

        if (dwFlags & MCI_BREAK_HWND)
            hwnd = lpBreakon->hwndBreak;
        else
            hwnd = 0;

        return  mciSetBreakKey (wDeviceID, lpBreakon->nVirtKey,
                                hwnd)
                    ? 0 : MMSYSERR_INVALPARAM;

    } else if (dwFlags & MCI_BREAK_OFF) {

        mciSetYieldProc(wDeviceID, NULL, 0);
        return 0;

    } else
        return MCIERR_MISSING_PARAMETER;
}

// Close the indicated device by sending a message inter-task
STATICFN DWORD PASCAL NEAR
mciAutoCloseDevice(
    LPCSTR lpstrDevice
    )
{
    LPSTR lpstrCommand;
    DWORD dwRet;

    if ((lpstrCommand =
            mciAlloc (sizeof (szClose) + 1 +
                    lstrlen (lpstrDevice))) == NULL)
        return MCIERR_OUT_OF_MEMORY;

    wsprintf(lpstrCommand, szCmdFormat, (LPCSTR)szClose, lpstrDevice);

    dwRet = mciSendSystemString (lpstrCommand, NULL, 0);

    mciFree (lpstrCommand);

    return dwRet;
}

//
// Process a single MCI command
//
// Called by mciSendCommandInternal
//
STATICFN DWORD PASCAL NEAR
mciSendSingleCommand(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwParam1,
    DWORD dwParam2,
    LPMCI_DEVICE_NODE nodeWorking,
    BOOL bTaskSwitch,
    LPMCI_INTERNAL_OPEN_INFO lpOpenInfo
    )
{
    DWORD dwRet;
#ifdef	DEBUG_RETAIL
    UINT wReturnType;
    DWORD dwTime;
#endif

#ifdef  DEBUG_RETAIL
    if (DebugmciSendCommand)
        wReturnType =
            mciDebugOut (wDeviceID, wMessage, dwParam1, dwParam2,
                         nodeWorking);
#endif

    switch (wMessage)
    {
        case MCI_OPEN:
            dwRet = mciOpenDevice (dwParam1,
                                   (LPMCI_OPEN_PARMS)dwParam2, lpOpenInfo);
            break;

        case MCI_CLOSE:
// If this device was auto opened send the command via a task switch
            if (bTaskSwitch)
            {
                if (dwParam1 & MCI_NOTIFY)
                    return MCIERR_NOTIFY_ON_AUTO_OPEN;

                dwRet = mciAutoCloseDevice (nodeWorking->lpstrName);
            } else
                dwRet =
                    mciCloseDevice (wDeviceID,
                                    dwParam1,
                                    (LPMCI_GENERIC_PARMS)dwParam2, TRUE);
            break;

        case MCI_SYSINFO:
            dwRet = mciSysinfo (wDeviceID, dwParam1,
                               (LPMCI_SYSINFO_PARMS)dwParam2);
            HandleNotify ((UINT)dwRet, 0, dwParam1, dwParam2);
            break;

        case MCI_BREAK:
            dwRet = mciBreak (wDeviceID, dwParam1,
                              (LPMCI_BREAK_PARMS)dwParam2);
            HandleNotify ((UINT)dwRet, wDeviceID, dwParam1, dwParam2);
            break;

        case MCI_SOUND:
        {
            dwRet =
                sndPlaySound (MCI_SOUND_NAME & dwParam1 ?
                              ((LPMCI_SOUND_PARMS)dwParam2)->lpstrSoundName : szSystemDefault,
                              dwParam1 & MCI_WAIT ?
                                    SND_SYNC : SND_ASYNC)
                    ? 0 : MCIERR_HARDWARE;
            HandleNotify ((UINT)dwRet, wDeviceID, dwParam1, dwParam2);
            break;
        }
        default:
#ifdef DEBUG_RETAIL
            if (DebugmciSendCommand)
            {
                dwTime = timeGetTime();
            }
#endif
// Initialize GetAsyncKeyState for break key
            if (dwParam1 & MCI_WAIT &&
                nodeWorking->fpYieldProc == mciBreakKeyYieldProc)
                GetAsyncKeyState (LOWORD(nodeWorking->dwYieldData));

            dwRet = (DWORD)SendDriverMessage(nodeWorking->hDrvDriver, wMessage,
                                   (LPARAM)dwParam1, (LPARAM)dwParam2);
#ifdef DEBUG_RETAIL
            if (DebugmciSendCommand)
            {
		dwTime = timeGetTime() - dwTime;
            }
#endif
            break;
    } // switch

#ifdef DEBUG_RETAIL
    if (DebugmciSendCommand)
    {
        if (dwRet & MCI_INTEGER_RETURNED) {
            wReturnType = MCI_INTEGER;
        }


        switch (wReturnType)
        {
            case MCI_INTEGER:
            {
                char strTemp[50];

                if (wMessage == MCI_OPEN) {

                    mciConvertReturnValue( wReturnType, HIWORD(dwRet),
                                           wDeviceID, (LPDWORD)dwParam2,
                                           strTemp, sizeof(strTemp));
                }
                else {
                    mciConvertReturnValue( wReturnType, HIWORD(dwRet),
                                           wDeviceID, (LPDWORD)dwParam2,
                                           strTemp, sizeof(strTemp));
                }

                RPRINTF2( "MMSYSTEM: time: %lums returns: \"%ls\"",
                          dwTime, (LPSTR)strTemp);
                break;
            }
            case MCI_STRING:
                RPRINTF2( "MMSYSTEM: time: %lums returns: \"%ls\"",
                          dwTime, (LPSTR)*(((LPDWORD)dwParam2) + 1));
                break;
        }
    }
#endif

    return dwRet;
}

// Internal version of mciSendCommand.  Differs ONLY in that the return
// value is a DWORD where the high word has meaning only for mciSendString

STATICFN DWORD NEAR PASCAL
mciSendCommandInternal(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwParam1,
    DWORD dwParam2,
    LPMCI_INTERNAL_OPEN_INFO lpOpenInfo
    )
{
    DWORD dwRetVal;
    LPMCI_DEVICE_NODE nodeWorking = NULL;
    BOOL bWalkAll;
    BOOL bTaskSwitch;
    DWORD dwAllError = 0;
    HTASK hCurrentTask;

    hCurrentTask = GetCurrentTask();

// If the device is "all" and the message is *not*
// "sysinfo" then we must walk all devices
    if (wDeviceID == MCI_ALL_DEVICE_ID && wMessage != MCI_SYSINFO && wMessage != MCI_SOUND)
    {
        if (wMessage == MCI_OPEN)
        {
            dwRetVal = MCIERR_CANNOT_USE_ALL;
            goto exitfn;
        }

        bWalkAll = TRUE;

// Start at device #1
        wDeviceID = 1;
    } else
        bWalkAll = FALSE;

// Walk through all devices if bWalkAll or just one device if !bWalkAll
    do
    {
// Initialize
        dwRetVal = 0;
        bTaskSwitch = FALSE;

// Validate the device ID if single device
        if (!bWalkAll)
        {
            if (!MCI_DO_NOT_NEED_OPEN(wMessage))
            {
                if (!MCI_VALID_DEVICE_ID(wDeviceID))
                {
                    dwRetVal = MCIERR_INVALID_DEVICE_ID;
                    goto exitfn;
                }
                nodeWorking = MCI_lpDeviceList[wDeviceID];
            }
        } else if (wMessage != MCI_SYSINFO)
            nodeWorking = MCI_lpDeviceList[wDeviceID];

// Skip if walking the device list and the
// device is not part of the current task

        if (bWalkAll)
        {
            if (nodeWorking == NULL ||
                nodeWorking->hOpeningTask != hCurrentTask)
                    goto no_send;
        }
// If the device is in the process of closing and the message
// is not MCI_CLOSE_DRIVER then return an error
        if (nodeWorking != NULL &&
            (nodeWorking->dwMCIFlags & MCINODE_ISCLOSING) &&
            wMessage != MCI_CLOSE_DRIVER)
        {
            dwRetVal = MCIERR_DEVICE_LOCKED;
            goto exitfn;
        }

// If this message is being sent from the wrong task (the device was auto-
// opened) fail all but the MCI_CLOSE message which gets sent inter-task
        if (nodeWorking != NULL &&
            nodeWorking->hCreatorTask != hCurrentTask)
            if (wMessage != MCI_CLOSE)
                return MCIERR_ILLEGAL_FOR_AUTO_OPEN;
            else
            {
// Don't even allow close from mciSendCommand if auto-open device has a
// pending close
                if (nodeWorking->dwMCIFlags & MCINODE_ISAUTOCLOSING)
                {
// But at least give the close a chance to take place
//!!                    Yield();
                    return MCIERR_DEVICE_LOCKED;
                } else
                    bTaskSwitch = TRUE;
            }

        dwRetVal = mciSendSingleCommand (wDeviceID, wMessage, dwParam1,
                                         dwParam2, nodeWorking, bTaskSwitch,
                                         lpOpenInfo);
no_send:

// If we are processing multiple devices
        if (bWalkAll)
        {
// If there was an error for this device
            if (dwRetVal != 0)
// If this is not the first error
                if (dwAllError != 0)
                    dwAllError = MCIERR_MULTIPLE;
// Just one error so far
                else
                    dwAllError = dwRetVal;
        }
    } while (bWalkAll && ++wDeviceID < MCI_wNextDeviceID);

exitfn:
// Return the accumulated error if multiple devices or just the single error
    return bWalkAll ? dwAllError : dwRetVal;
}


/*
 * @doc EXTERNAL MCI
 *
 * @api DWORD | mciSendCommand | This function sends a command message to
 * the specified MCI device.
 *
 * @parm UINT | wDeviceID | Specifies the device ID of the MCI device to
 * receive the command. This parameter is
 *  not used with the <m MCI_OPEN> command.
 *
 * @parm UINT | wMessage | Specifies the command message.
 *
 * @parm DWORD | dwParam1 | Specifies flags for the command.
 *
 * @parm DWORD | dwParam2 | Specifies a pointer to a parameter block
 *  for the command.
 *
 * @rdesc Returns zero if the function was successful.  Otherwise, it returns
 *  error information. The low-order word
 *  of the returned DWORD is the error return value. If the error is
 *  device-specific, the high-order word contains the driver ID; otherwise
 *  the high-order word is zero.
 *
 *  To get a textual description of <f mciSendCommand> return values,
 *  pass the return value to <f mciGetErrorString>.
 *
 *  Error values that are returned when a device is being opened
 *  are listed with the MCI_OPEN message. In addition to the
 *  MCI_OPEN error returns, this function can
 *  return the following values:
 *
 *  @flag MCIERR_BAD_TIME_FORMAT | Illegal value for time format.
 *
 *  @flag MCIERR_CANNOT_USE_ALL | The device name "all" is not allowed
 *  for this command.
 *
 *  @flag MCIERR_CREATEWINDOW | Could not create or use window.
 *
 *  @flag MCIERR_DEVICE_LOCKED | The device is locked until it is
 *  closed automatically.
 *
 *  @flag MCIERR_DEVICE_NOT_READY | Device not ready.
 *
 *  @flag MCIERR_DEVICE_TYPE_REQUIRED | The device name must be a valid
 *  device type.
 *
 *  @flag MCIERR_DRIVER | Unspecified device error.
 *
 *  @flag MCIERR_DRIVER_INTERNAL | Internal driver error.
 *
 *  @flag MCIERR_FILE_NOT_FOUND | Requested file not found.
 *
 *  @flag MCIERR_FILE_NOT_SAVED | The file was not saved.
 *
 *  @flag MCIERR_FILE_READ | A read from the file failed.
 *
 *  @flag MCIERR_FILE_WRITE | A write to the file failed.
 *
 *  @flag MCIERR_FLAGS_NOT_COMPATIBLE | Incompatible parameters
 *  were specified.
 *
 *  @flag MCIERR_HARDWARE | Hardware error on media device.
 *
 *  @flag MCIERR_INTERNAL | mmsystem startup error.
 *
 *  @flag MCIERR_INVALID_DEVICE_ID | Invalid device ID.
 *
 *  @flag MCIERR_INVALID_DEVICE_NAME | The device is not open
 *  or is not known.
 *
 *  @flag MCIERR_INVALID_FILE | Invalid file format.
 *
 *  @flag MCIERR_MULTIPLE | Errors occurred in more than one device.
 *
 *  @flag MCIERR_NO_WINDOW | There is no display window.
 *
 *  @flag MCIERR_NULL_PARAMETER_BLOCK | Parameter block pointer was NULL.
 *
 *  @flag MCIERR_OUT_OF_MEMORY | Not enough memory for requested operation.
 *
 *  @flag MCIERR_OUTOFRANGE | Parameter value out of range.
 *
 *  @flag MCIERR_UNNAMED_RESOURCE | Attempt to save unnamed file.
 *
 *  @flag MCIERR_UNRECOGNIZED_COMMAND | Unknown command.
 *
 *  @flag MCIERR_UNSUPPORTED_FUNCTION | Action not available for this
 *  device.
 *
 *  The following additional return values are defined for MCI sequencers:
 *
 *  @flag MCIERR_SEQ_DIV_INCOMPATIBLE | Set Song Pointer incompatible
 *  with SMPTE files.
 *
 *  @flag MCIERR_SEQ_PORT_INUSE | Specified port is in use.
 *
 *  @flag MCIERR_SEQ_PORT_MAPNODEVICE | Current map uses non-existent
 *  device.
 *
 *  @flag MCIERR_SEQ_PORT_MISCERROR | Miscellaneous error with
 *  specified port.
 *
 *  @flag MCIERR_SEQ_PORT_NONEXISTENT | Specified port does not exist.
 *
 *  @flag MCIERR_SEQ_PORTUNSPECIFIED | No current MIDI port.
 *
 *  @flag MCIERR_SEQ_NOMIDIPRESENT | No MIDI ports present.
 *
 *  @flag MCIERR_SEQ_TIMER | Timer error.
 *
 *  The following additional return values are defined for MCI waveform
 *  audio devices:
 *
 *  @flag MCIERR_WAVE_INPUTSINUSE | No compatible waveform recording
 *   device is free.
 *
 *  @flag MCIERR_WAVE_INPUTSUNSUITABLE | No compatible waveform
 *  recording devices.
 *
 *  @flag MCIERR_WAVE_INPUTUNSPECIFIED | Any compatible waveform
 *  recording device may be used.
 *
 *  @flag MCIERR_WAVE_OUTPUTSINUSE | No compatible waveform playback
 *  device is free.
 *
 *  @flag MCIERR_WAVE_OUTPUTSUNSUITABLE | No compatible waveform
 *  playback devices.
 *
 *  @flag MCIERR_WAVE_OUTPUTUNSPECIFIED | Any compatible waveform
 *  playback device may be used.
 *
 *  @flag MCIERR_WAVE_SETINPUTINUSE | Set waveform recording device
 *  is in use.
 *
 *  @flag MCIERR_WAVE_SETINPUTUNSUITABLE | Set waveform recording
 *  device is incompatible with set format.
 *
 *  @flag MCIERR_WAVE_SETOUTPUTINUSE | Set waveform playback device
 *  is in use.
 *
 *  @flag MCIERR_WAVE_SETOUTPUTUNSUITABLE | Set waveform playback
 *  device is incompatible with set format.
 *
 * @comm Use the <m MCI_OPEN> command to obtain the device ID
 *  specified by <p wDeviceID>.
 *
 * @xref mciGetErrorString mciSendString
 */

 /*
 * @doc internal
 *
 * @api DWORD | mciDriverEntry | Actually a callback.  The entry point for MCI drivers.
 *
 * @parm UINT | wMessage | Identifies the requested action to be performed.
 *
 * @parm DWORD | dwParam1 | Specifies data for this message.  Defined separately
 * for each message.
 *
 * @parm DWORD | dwParam2 | Specifies data for this message.  Defined separately
 * for each message.
 *
 * @rdesc The return value is defined separately for each message.
 */
DWORD WINAPI
mciSendCommand(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwParam1,
    DWORD dwParam2
    )
{
    // Initialize the 16-bit device list if needed.
    if (!MCI_bDeviceListInitialized && !mciInitDeviceList())
        return MCIERR_OUT_OF_MEMORY;

    // MCI_OPEN_DRIVER & MCI_CLOSE_DRIVER only supported on 16-bit drivers
    if ( (wMessage == MCI_OPEN_DRIVER) || (wMessage == MCI_CLOSE_DRIVER) ) {
        return mciSendCommand16( wDeviceID, wMessage, dwParam1, dwParam2 );
    }

    /*
    ** If we are opening the device try the 32 bit side first.  If this
    ** worked (hopefully this is the usual case) we return the given
    ** device ID.  Otherwise, we try for a 16 bit device.
    */
    if ( wMessage == MCI_OPEN ) {

        DWORD dwErr;

        DPRINTF(("mciSendCommand: Got an MCI_OPEN command... "
                 "trying 32 bits\r\n" ));

        dwErr = mciMessage( THUNK_MCI_SENDCOMMAND, (DWORD)wDeviceID,
                            (DWORD)wMessage, dwParam1, dwParam2 );

        if ( dwErr == MMSYSERR_NOERROR ) {

            LPMCI_OPEN_PARMS lpOpenParms = (LPMCI_OPEN_PARMS)dwParam2;

            DPRINTF(("mciSendCommand: We have a 32 bit driver,"
                     " devID = 0x%X\r\n", lpOpenParms->wDeviceID ));

            return dwErr;

        }
        else {

            /*
            ** We could open the device on the 32 bit side so let
            ** the 16 bit code have a go (ie. just fall thru to the code below).
            */
            DPRINTF(("mciSendCommand: Could not find a 32 bit driver, "
                     "trying for a 16 bit driver\r\n" ));

            dwErr = mciSendCommand16( wDeviceID, wMessage, dwParam1, dwParam2 );

            if ( dwErr == MMSYSERR_NOERROR ) {

                LPMCI_OPEN_PARMS lpOpenParms = (LPMCI_OPEN_PARMS)dwParam2;

                DPRINTF(("mciSendCommand: We have a 16 bit driver,"
                         " devID = 0x%X\r\n", lpOpenParms->wDeviceID ));
            }

            return dwErr;
        }
    }
    else {

        DWORD dwErr16;
        DWORD dwErr32;

        /*
        ** If we have been given the MCI_ALL_DEVICE_ID then we have to
        ** send the command to both the 32 and 16 bit side.
        **
        ** Special care needs to be taken with the MCI_ALL_DEVICE_ID.
        ** The message must be passed on to both 32 and 16 bit devices.
        */

        if (CouldBe16bitDrv(wDeviceID)) {
            dwErr16 = mciSendCommand16( wDeviceID, wMessage,
                                        dwParam1, dwParam2 );

            if ( wDeviceID != MCI_ALL_DEVICE_ID ) {
                return dwErr16;
            }
        }

        dwErr32 = mciMessage( THUNK_MCI_SENDCOMMAND, (DWORD)wDeviceID,
                              (DWORD)wMessage, dwParam1, dwParam2 );

        /*
        ** If we have the MCI_ALL_DEVICE_ID device ID we only return
        ** an error if both the 16 and 32 bit calls failed.  In which
        ** case we return the 32 bit error code.
        */
        if ( wDeviceID == MCI_ALL_DEVICE_ID ) {

            if ( (dwErr16 != MMSYSERR_NOERROR)
              && (dwErr32 != MMSYSERR_NOERROR) ) {

                return dwErr32;
            }
            else {
                return MMSYSERR_NOERROR;
            }
        }

        return dwErr32;
    }
}


/*****************************Private*Routine******************************\
* mciSendCommand16
*
* Here is where we execute the real 16 bit mciSendCommand.  Hoefully this
* will not get called to often.
*
* History:
* dd-mm-94 - StephenE - Created
*
\**************************************************************************/
DWORD WINAPI
mciSendCommand16(
    UINT wDeviceID,
    UINT wMessage,
    DWORD dwParam1,
    DWORD dwParam2
    )
{
    DWORD dwErr;
    MCI_INTERNAL_OPEN_INFO OpenInfo;


    //
    // Send the command.  This shell is responsible for adding the device ID
    // to the error code if necessary
    //
    OpenInfo.hCallingTask = GetCurrentTask();
    OpenInfo.lpstrParams = NULL;
    OpenInfo.lpstrPointerList = NULL;
    OpenInfo.wParsingError = 0;
    dwErr = mciSendCommandInternal (wDeviceID, wMessage,
                                    dwParam1, dwParam2, &OpenInfo);
    //
    // If the return value contains a resource ID then clear
    // it from clear the high word
    //
    if (dwErr & MCI_RESOURCE_RETURNED)
        ((LPDWORD)dwParam2)[1] &= 0xFFFF;
    dwErr &= 0xFFFF;

    //
    // If the error message is in a driver, store the driver ID in the high
    // word of the error code
    //
    if ((UINT)dwErr >= MCIERR_CUSTOM_DRIVER_BASE)
        dwErr |= ((DWORD)wDeviceID << 16);

#ifdef DEBUG
    // Dump the error text if any to the debug terminal
    if (dwErr != 0)
    {
        char strTemp[MAXERRORLENGTH];

        if (!mciGetErrorString (dwErr, strTemp, sizeof(strTemp)))
            LoadString(ghInst, STR_MCISCERRTXT, strTemp, sizeof(strTemp));
        else
            DPRINTF(("mciSendCommand: %ls\r\n",(LPSTR)strTemp));
    }
#endif
    return dwErr;
}



// Grab colonized digit
// Return is number of bytes written to output (NOT including NULL)
// or 0 if out of room in output buffer (but is terminated anyway)
// If there is room then at least two digits are written, padded with '0'
// if necessary.  The function assumes that the buffer size is non-zero length,
// as this is checked in the calling function.
STATICFN UINT PASCAL NEAR
mciColonizeDigit(
    LPSTR lpstrOutput,
    unsigned char cDigit,
    UINT wSize
    )
{
    UINT wCount;

    wCount = 2;

// If there is room for at least two digits
    if (wSize >= 3)
    {
        if (cDigit >= 100)
        {
            wCount = 3;
            if (wSize < 4)
                goto terminate;
            *lpstrOutput++ = (char)((cDigit / 100) % 10 + '0');
            cDigit = (char)(cDigit % 100);
        }
        *lpstrOutput++ = (char)(cDigit / 10 + '0');
        *lpstrOutput++ = (char)(cDigit % 10 + '0');
    }

terminate:
    *lpstrOutput++ = '\0';

// If we ran out of room then return an error
    return (wCount >= wSize) ? 0 : wCount;
}

/*
 * @doc INTERNAL MCI
 * @func BOOL | mciColonize | Convert a colonized dword into a string
 * representation
 *
 * @parm LPSTR | lpstrOutput | Output buffer
 *
 * @parm UINT | wLength | Size of output buffer
 *
 * @parm DWORD | dwData | Value to convert
 *
 * @parm UINT | wType | Either MCI_COLONIZED3_RETURN or
 * MCI_COLONIZED4_RETURN is set (HIWORD portion only!)
 *
 * @comm Example:  For C4, 0x01020304 is converted to "04:03:02:01"
 *                 For C3, 0x01020304 is converted to "04:03:02"
 *
 * @rdesc FALSE if there is not enough room in the output buffer
 *
 */
STATICFN BOOL PASCAL NEAR mciColonize(
    LPSTR lpstrOutput,
    UINT wLength,
    DWORD dwData,
    UINT wType
    )
{
    LPSTR lpstrInput = (LPSTR)&dwData;
    UINT wSize;
    int i;

    for (i = 1; i <= (wType & HIWORD(MCI_COLONIZED3_RETURN) ? 3 : 4); ++i)
    {
        wSize = mciColonizeDigit (lpstrOutput,
                                  *lpstrInput++,
                                  wLength);
        if (wSize == 0)
            return FALSE;
        lpstrOutput += wSize;
        wLength -= wSize;
        if (i < 3 || i < 4 && wType & HIWORD(MCI_COLONIZED4_RETURN))
        {
            --wLength;
            if (wLength == 0)
                return FALSE;
            else
                *lpstrOutput++ = ':';
        }
    }
    return TRUE;
}

//
// Convert the return value to a return string
//
STATICFN UINT PASCAL NEAR
mciConvertReturnValue(
    UINT wType,
    UINT wErrCode,
    UINT wDeviceID,
    LPDWORD   dwParams,
    LPSTR lpstrReturnString,
    UINT wReturnLength
    )
{
    UINT    wExternalTable;

    if (lpstrReturnString == NULL || wReturnLength == 0)
        return 0;

    switch (wType)
    {
        case MCI_INTEGER:
// Convert integer or resource return value to string
            if (wErrCode & HIWORD(MCI_RESOURCE_RETURNED))
            {
                int nResId = HIWORD(dwParams[1]);
                LPMCI_DEVICE_NODE nodeWorking;
                HINSTANCE hInstance;

                if ((nodeWorking = MCI_lpDeviceList[wDeviceID])
                    == NULL)
                {
// Return blank string on memory error
                    DOUT ("mciConvertReturnValue Warning:NULL device node\r\n");
                    break;
                }

// Return value is a resource
                if (wErrCode & HIWORD(MCI_RESOURCE_DRIVER))
                {
// Return string ID belongs to driver
                    hInstance = nodeWorking->hDriver;

                    wExternalTable = nodeWorking->wCustomCommandTable;
                } else
                {
                    wExternalTable = nodeWorking->wCommandTable;
                    hInstance = ghInst;
                }

// Try to get string from custom or device specific external table
                if (wExternalTable == -1 ||
                    command_tables[wExternalTable].hModule == NULL ||
                    LoadString (command_tables[wExternalTable].hModule,
                                nResId, lpstrReturnString, wReturnLength)
                    == 0)
                {
// Try to get string from CORE.MCI if it's not from the driver
                    if (hInstance != ghInst ||
                        command_tables[0].hModule == NULL ||
                        LoadString (command_tables[0].hModule,
                                    nResId, lpstrReturnString, wReturnLength)
                        == 0)
// Get string from custom module or MMSYSTEM.DLL
                        LoadString (hInstance, nResId, lpstrReturnString,
                                    wReturnLength);
                }

            } else if (wErrCode & HIWORD(MCI_COLONIZED3_RETURN) ||
                        wErrCode & HIWORD(MCI_COLONIZED4_RETURN))
            {
                if (!mciColonize (lpstrReturnString,
                                wReturnLength, dwParams[1], wErrCode))
                    return MCIERR_PARAM_OVERFLOW;
            } else
// Convert integer return value to string
            {
                DWORD dwTemp;

// Need room for a sign, up to ten digits and a NULL
                if (wReturnLength < 12)
                    return MCIERR_PARAM_OVERFLOW;

                if (wType == MCI_STRING ||
                    wErrCode == HIWORD(MCI_INTEGER_RETURNED))
                    dwTemp = *(LPDWORD)dwParams[1];
                else
                    dwTemp = dwParams[1];
                wsprintf(lpstrReturnString, szLongFormat, dwTemp);
            }
            break;
        case MCI_RECT:
// Need from for 4 times (a sign plus 5 digits) plus three spaces and a NULL
            if (wReturnLength < 4 * 6 + 4)
                return MCIERR_PARAM_OVERFLOW;

            wsprintf (lpstrReturnString, szRectFormat,
                        ((LPWORD)dwParams)[2], ((LPWORD)dwParams)[3],
                        ((LPWORD)dwParams)[4], ((LPWORD)dwParams)[5]);
            break;
        default:
// Only support INTEGERs & MIXED
            DOUT ("mciConvertReturnValue Warning:  Unknown return type\r\n");
            return MCIERR_PARSER_INTERNAL;
    }
    return 0;
}


//
// Pull off the command name and device name from the command string,
// leaving *lplpstrCommand pointing past the device name
//
// Returns 0 or an error code on failure.  If successful, the caller must
// free the pstrCommandName and pstrDeviceName
//
// If bCompound then check for a '!' separator in the extracted device name
// and return only the element part.  This is done so that inter-task
// commands to auto-opened devices will include the correct device name
//
STATICFN DWORD PASCAL NEAR
mciSeparateCommandParts(
    LPSTR FAR *lplpstrCommand,
    BOOL bCompound,
    LPSTR FAR *lplpstrCommandName,
    LPSTR FAR *lplpstrDeviceName
    )
{
    LPSTR lpstrCommand;
    UINT wErr;

// Localize the input
    lpstrCommand = *lplpstrCommand;

// Remove leading spaces

    while (*lpstrCommand == ' ')
        ++lpstrCommand;

    if (*lpstrCommand == '\0')
        return MCIERR_MISSING_COMMAND_STRING;

// Pull the command name off of the command string
   if ((wErr = mciEatToken (&lpstrCommand, ' ', lplpstrCommandName, FALSE))
       != 0)
       return wErr;

// Skip past spaces
    while (*lpstrCommand == ' ')
        ++lpstrCommand;

// If we're looking for compound elements then yank off any leading
// device type if it is not the open command
    if (bCompound && lstrcmpi (szOpen, *lplpstrCommandName) != 0)
    {
        LPSTR lpstrTemp = lpstrCommand;
        while (*lpstrTemp != '\0')
        {
            if (*lpstrTemp == '!')
            {
// A ! was found so skip past it
                lpstrCommand = lpstrTemp + 1;
                break;
            } else
                ++lpstrTemp;
        }
    }

// Pull the device name off of the command string
    if ((wErr = mciEatToken (&lpstrCommand, ' ', lplpstrDeviceName, FALSE))
        != 0)
    {
        mciFree (*lplpstrCommandName);
        return wErr;

    }

// Fix up the results
    *lplpstrCommand = lpstrCommand;

    return 0;
}

STATICFN DWORD NEAR PASCAL
mciSendSystemString(
    LPCSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength
    )
{
    DWORD    dwRet;
    LPMCI_SYSTEM_MESSAGE    lpMessage;

    if (!hwndNotify)
        return MCIERR_INTERNAL;
    if (lpMessage = mciAlloc (sizeof (MCI_SYSTEM_MESSAGE))) {
        LPSTR    lpstrPath;

        if (lpstrPath = mciAlloc(MAX_PATHNAME)) {
            if (!(DosGetCurrentDir(0, lpstrPath))) {
                lpMessage->lpstrCommand = (LPSTR)lpstrCommand;
                lpMessage->lpstrReturnString = lpstrReturnString;
                lpMessage->wReturnLength = wReturnLength;
                lpMessage->hCallingTask = GetCurrentTask();
                lpMessage->lpstrNewDirectory = lpstrPath;
                lpMessage->nNewDrive = DosGetCurrentDrive();
                dwRet = (DWORD)SendMessage(hwndNotify, MM_MCISYSTEM_STRING, (WPARAM)0, (LPARAM)lpMessage);
            } else {
                DOUT("mciSendSystemString: cannot get current directory\r\n");
                dwRet = MCIERR_GET_CD;
            }
            mciFree(lpstrPath);
        } else {
            DOUT("mciSendSystemString: cannot allocate new path\r\n");
            dwRet = MCIERR_OUT_OF_MEMORY;
        }
        mciFree(lpMessage);
    } else {
        DOUT("mciSendSystemString: cannot allocate message block\r\n");
        dwRet = MCIERR_OUT_OF_MEMORY;
    }
    return dwRet;
}

DWORD FAR PASCAL
mciRelaySystemString(
    LPMCI_SYSTEM_MESSAGE lpMessage
    )
{
    DWORD    dwRet;
    LPSTR    lpstrOldPath;

    if (lpstrOldPath = mciAlloc(MAX_PATHNAME)) {
        if (!(DosGetCurrentDir(0, lpstrOldPath))) {
            int    nOldDrive;

            nOldDrive = DosGetCurrentDrive();
            if (DosSetCurrentDrive(lpMessage->nNewDrive)) {
                if (DosChangeDir(lpMessage->lpstrNewDirectory) == 1) {
                    dwRet = mciSendStringInternal (NULL, NULL, 0, 0, lpMessage);
                    if (!DosSetCurrentDrive(nOldDrive))
                        DOUT("mciRelaySystemString: WARNING, cannot restore drive\r\n");
                    if (DosChangeDir(lpstrOldPath) != 1)
                        DOUT("mciRelaySystemString: WARNING, cannot restore directory\r\n");
                } else {
                    DosSetCurrentDrive(nOldDrive);
                    DOUT("mciRelaySystemString: cannot change to new directory\r\n");
                    dwRet = MCIERR_SET_CD;
                }
            } else {
                DOUT("mciRelaySystemString: cannot change to new drive\r\n");
                dwRet = MCIERR_SET_DRIVE;
            }
        } else {
            DOUT("mciRelaySystemString: cannot get old directory\r\n");
            dwRet = MCIERR_GET_CD;
        }
        mciFree(lpstrOldPath);
    } else {
        DOUT("mciRelaySystemString: cannot allocate old path\r\n");
        dwRet = MCIERR_OUT_OF_MEMORY;
    }
    return dwRet;
}

// Returns TRUE if "notify" is contained in string with leading blank
// and trailing blank or '\0'
STATICFN BOOL PASCAL NEAR
mciFindNotify(
    LPSTR lpString
    )
{
    while (*lpString != '\0')
    {
// "notify" must be preceded by a blank
        if (*lpString++ == ' ')
        {
            LPSTR lpTemp;

            lpTemp = szNotify;
            while (*lpTemp != '\0' && *lpString != '\0' &&
                   *lpTemp == MCI_TOLOWER(*lpString))
            {
                ++lpTemp;
                ++lpString;
            }
// "notify" must be followed by a blank or a null
            if (*lpTemp == '\0' &&
                (*lpString == '\0' || *lpString == ' '))
                return TRUE;
        }
    }
    return FALSE;
}

/*
 * @doc INTERNAL MCI
 *
 * @func UINT | mciAutoOpenDevice | Try to auto-open the given device and
 * then send the given command with notification sent to the system task
 * window proc which sends a close command to the device on receipt
 *
 * @parm LPSTR | lpstrDeviceName | The device name to open
 *
 * @parm LPSTR | lpstrCommand | The full command to send including the
 * device name which must be the same as lpstrDeviceName
 *
 * @parm LPSTR | lpstrReturnString | The caller's return string buffer
 *
 * @parm UINT | wReturnLength | Size of the caller's return string buffer
 *
 * @rdesc The errorcode to return to the user
 */
STATICFN UINT PASCAL NEAR
mciAutoOpenDevice(
    LPSTR lpstrDeviceName,
    LPSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength
    )
{
    LPSTR lpstrTempCommand, lpstrTempReturn = NULL;
    UINT wErr;

// "notify" not allowed.  This will be found by the parser but the wrong
// error message will be returned.
    if (mciFindNotify (lpstrCommand))
        return MCIERR_NOTIFY_ON_AUTO_OPEN;

// Build the command string "open <device name>"

// Must be GMEM_SHARE for system task
// device name + blank + "open"
    if ((lpstrTempCommand = mciAlloc (lstrlen (lpstrDeviceName) + 1 +
                                        sizeof (szOpen)))
        == NULL)
        return MCIERR_OUT_OF_MEMORY;

    wsprintf(lpstrTempCommand, szCmdFormat, (LPSTR)szOpen, lpstrDeviceName);
// Get the open string into the system task via a SendMessage() to mmWndProc
    wErr = (UINT)mciSendSystemString (lpstrTempCommand, NULL, NULL);

    mciFree (lpstrTempCommand);

    if (wErr != 0)
        return wErr;

    lpstrTempCommand = NULL;
// Must make a GMEM_SHARE copy of the return string for system task
    if (lpstrReturnString == NULL ||
        (lpstrTempReturn = mciAlloc (wReturnLength + 1)) != NULL)
    {
// Build a GMEM_SHARE command string "<user command> <notify>
// command + blank + "notify"
        if ((lpstrTempCommand = mciAlloc (lstrlen (lpstrCommand) + 1 + sizeof(szNotify))) == NULL)
            mciFree (lpstrTempReturn);
    }

    if (lpstrTempCommand == NULL)
    {
// Close the device
        mciDriverNotify (hwndNotify, mciGetDeviceID (lpstrDeviceName), 0);
        return MCIERR_OUT_OF_MEMORY;
    }

    wsprintf(lpstrTempCommand, szCmdFormat, lpstrCommand, (LPSTR)szNotify);

// Get the user command string into the system task via a SendMessage()
// to mmWndProc
// The notification handle is also mmWndProc
    wErr = (UINT)mciSendSystemString (lpstrTempCommand, lpstrTempReturn,
                                    wReturnLength);

// Copy the return string into the user's buffer
    if (lpstrReturnString != NULL)
    {
        lstrcpy (lpstrReturnString, lpstrTempReturn);
        mciFree (lpstrTempReturn);
    }

    mciFree (lpstrTempCommand);

// If there was an error we must close the device
    if (wErr != 0)
        mciAutoCloseDevice (lpstrDeviceName);

    return wErr;
}

//
// Identical to mciSendString() but the lpMessage parameter is tacked on
//
// lpMessage comes from inter-task mciSendString and includes an
// hCallingTask item which is sent down the the OPEN command
//
STATICFN DWORD NEAR PASCAL
mciSendStringInternal(
    LPCSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength,
    HWND hwndCallback,
    LPMCI_SYSTEM_MESSAGE lpMessage
    )
{
    UINT    wID, wConvertReturnValue, wErr, wMessage;
    UINT    wLen;
    UINT    wDeviceID;
    LPDWORD lpdwParams = NULL;
    DWORD   dwReturn, dwFlags = 0;
    LPSTR   lpCommandItem;
    DWORD   dwErr, dwRetType;
    UINT    wTable = (UINT)-1;
    LPSTR    lpstrDeviceName = NULL, lpstrCommandName = NULL;
    LPSTR   FAR *lpstrPointerList = NULL;
    LPSTR   lpstrCommandStart;
    HTASK hCallingTask;
    UINT    wParsingError;
    BOOL    bNewDevice;
    LPSTR   lpstrInputCopy;

    // Did this call come in from another task
    if (lpMessage != NULL)
    {
        // Yes so restore info
        lpstrCommand = lpMessage->lpstrCommand;
        lpstrReturnString = lpMessage->lpstrReturnString;
        wReturnLength = lpMessage->wReturnLength;
        hwndCallback = hwndNotify;
        hCallingTask = lpMessage->hCallingTask;
        lpstrInputCopy = NULL;

    } else
    {
        BOOL bInQuotes = FALSE;

        // No so set hCallingTask to current
        hCallingTask = GetCurrentTask();

        if (lpstrCommand == NULL)
            return MCIERR_MISSING_COMMAND_STRING;

        // Make a copy of the input string and convert tabs to
        // spaces except those inside quotes
        //
        if ((lpstrInputCopy = mciAlloc (lstrlen (lpstrCommand) + 1)) == NULL)
            return MCIERR_OUT_OF_MEMORY;
        lstrcpy (lpstrInputCopy, lpstrCommand);
        lpstrCommand = lpstrInputCopy;
        lpstrCommandStart = (LPSTR)lpstrCommand;
        while (*lpstrCommandStart != '\0')
        {
            if (*lpstrCommandStart == '"')
                bInQuotes = !bInQuotes;
            else if (!bInQuotes && *lpstrCommandStart == '\t')
                *lpstrCommandStart = ' ';
            ++lpstrCommandStart;
        }
    }
    lpstrCommandStart = (LPSTR)lpstrCommand;

    if (lpstrReturnString == NULL) {
        //
        // As an additional safeguard against the driver writing into the
        // output buffer when the return string pointer is NULL, set its
        // length to 0
        //
        wReturnLength = 0;
    }
    else {
        //
        // Set return to empty string so that it won't print out garbage if not
        // touched again
        //
        *lpstrReturnString = '\0';
    }

    // Pull the command name and device name off the command string
    if ((dwReturn = mciSeparateCommandParts (&lpstrCommand, lpMessage != NULL,
                                   &lpstrCommandName, &lpstrDeviceName)) != 0)
        goto exitfn;

    // Get the device id (if any) of the given device name
    wDeviceID = mciGetDeviceIDInternal(lpstrDeviceName, hCallingTask);

    // Allow "new" for an empty device name
    if (wDeviceID == 0 && lstrcmpi (lpstrDeviceName, szNew) == 0)
    {
        bNewDevice = TRUE;
        *lpstrDeviceName = '\0';
    } else {
        bNewDevice = FALSE;
    }


    // Look up the command name
    wMessage = mciParseCommand (wDeviceID, lpstrCommandName, lpstrDeviceName,
                                &lpCommandItem, &wTable);

    // If the device has a pending auto-close
    if (MCI_VALID_DEVICE_ID(wDeviceID))
    {
        LPMCI_DEVICE_NODE nodeWorking = MCI_lpDeviceList[wDeviceID];

        // Is there a pending auto-close message?
        if (nodeWorking->dwMCIFlags & MCINODE_ISAUTOCLOSING)
        {
            // Let the device close
            //!!            Yield();
            // Did the device close?
            //!!            wDeviceID = mciGetDeviceIDInternal (lpstrDeviceName, hCallingTask);
            // If not then fail this command
            //!!            if (wDeviceID == 0)
            //!!            {

            wErr = MCIERR_DEVICE_LOCKED;
            goto cleanup;

            //!!            }

            // If the call does not come from another task and is not owned by this task
            // and is not the SYSINFO command
            //

        } else if (lpMessage == NULL &&
            nodeWorking->hOpeningTask != nodeWorking->hCreatorTask &&
            wMessage != MCI_SYSINFO)
        // Send the string inter-task
        {
            if (mciFindNotify (lpstrCommandStart))
            {
                wErr = MCIERR_NOTIFY_ON_AUTO_OPEN;
                goto cleanup;
            } else
            {
                LPSTR    lpstrReturnStringCopy;

                mciFree(lpstrCommandName);
                mciFree(lpstrDeviceName);
                mciUnlockCommandTable (wTable);

                if ((lpstrReturnStringCopy = mciAlloc (wReturnLength + 1)) != NULL)
                {
                    dwReturn = mciSendSystemString (lpstrCommandStart,
                                                    lpstrReturnStringCopy,
                                                    wReturnLength);
                    lstrcpy (lpstrReturnString, lpstrReturnStringCopy);
                    mciFree (lpstrReturnStringCopy);
                } else
                    dwReturn = MCIERR_OUT_OF_MEMORY;
                goto exitfn;
            }
        }
    }

    // There must be a device name (except for the MCI_SOUND message)
    if (*lpstrDeviceName == '\0' && wMessage != MCI_SOUND && !bNewDevice)
    {
        wErr = MCIERR_MISSING_DEVICE_NAME;
        goto cleanup;
    }

    // The command must appear in the parser tables
    if (wMessage == 0)
    {
        wErr = MCIERR_UNRECOGNIZED_COMMAND;
        goto cleanup;
    }

    // The "new" device name is only legal for the open message
    if (bNewDevice)
    {
        if (wMessage != MCI_OPEN)
        {
            wErr = MCIERR_INVALID_DEVICE_NAME;
            goto cleanup;
        }
    }

    // If there was no device ID
    if (wDeviceID == 0)
        // If auto open is not legal (usually internal commands)
        if (MCI_CANNOT_AUTO_OPEN (wMessage))
        {
            // If the command needs an open device
            if (!MCI_DO_NOT_NEED_OPEN (wMessage))
            {
                wErr = MCIERR_INVALID_DEVICE_NAME;
                goto cleanup;
            }
        } else

        // If auto open is legal try to open the device automatically
        {
            wErr = mciAutoOpenDevice (lpstrDeviceName, lpstrCommandStart,
                                      lpstrReturnString, wReturnLength);
            goto cleanup;
        }

    //
    //   Parse the command parameters
    //
    // Allocate command parameter block
    if ((lpdwParams = (LPDWORD)mciAlloc (sizeof(DWORD) * MCI_MAX_PARAM_SLOTS))
        == NULL)
    {
        wErr = MCIERR_OUT_OF_MEMORY;
        goto cleanup;
    }

    wErr = mciParseParams (lpstrCommand, lpCommandItem,
                            &dwFlags,
                            (LPSTR)lpdwParams,
                            MCI_MAX_PARAM_SLOTS * sizeof(DWORD),
                            &lpstrPointerList, &wParsingError);
    if (wErr != 0)
        goto cleanup;

    // The 'new' device keyword requires an alias
    if (bNewDevice && !(dwFlags & MCI_OPEN_ALIAS))
    {
        wErr = MCIERR_NEW_REQUIRES_ALIAS;
        goto cleanup;
    }

    // Parsed OK so execute command

    // Special processing for the MCI_OPEN message's parameters
    if (wMessage == MCI_OPEN)
    {
        // Manually reference the device type and device element
        if (dwFlags & MCI_OPEN_TYPE)
        {
            // The type name was specified explicitly as a parameter
            // so the given device name is the element name
            ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrElementName
                = (LPSTR)lpstrDeviceName;
            dwFlags |= MCI_OPEN_ELEMENT;
        } else
        {
            // A type must be explicitly specified when "new" is used
            if (bNewDevice)
            {
                wErr = MCIERR_INVALID_DEVICE_NAME;
                goto cleanup;
            }
            // The device type is the given device name.
            // There is no element name
            ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrDeviceType
                = (LPSTR)lpstrDeviceName;
            ((LPMCI_OPEN_PARMS)lpdwParams)->lpstrElementName = NULL;
            dwFlags |= MCI_OPEN_TYPE;
        }
    }

    else if (wMessage == MCI_SOUND && *lpstrDeviceName != '\0')
    {
        // Kludge the sound name for SOUND
        //!!        mciToLower (lpstrDeviceName);
        if (lstrcmpi (lpstrDeviceName, szNotify) == 0)
            dwFlags |= MCI_NOTIFY;
        else if (lstrcmpi (lpstrDeviceName, szWait) == 0)
            dwFlags |= MCI_WAIT;
        else
        {
            ((LPMCI_SOUND_PARMS)lpdwParams)->lpstrSoundName = lpstrDeviceName;
            dwFlags |= MCI_SOUND_NAME;
        }
    }

    // Figure out what kind of return value to expect

    // Initialize flag
    wConvertReturnValue = 0;

    // Skip past header
    wLen = mciEatCommandEntry (lpCommandItem, NULL, NULL);

    // Get return value (if any)
    mciEatCommandEntry (lpCommandItem + wLen, &dwRetType, &wID);
    if (wID == MCI_RETURN)
    {
        // There is a return value
        if (wDeviceID == MCI_ALL_DEVICE_ID && wMessage != MCI_SYSINFO)
        {
            wErr = MCIERR_CANNOT_USE_ALL;
            goto cleanup;
        }
        switch ((UINT)dwRetType)
        {
            case MCI_STRING:
                // The return value is a string, point output
                // buffer to user's buffer
                lpdwParams[1] = (DWORD)lpstrReturnString;
                lpdwParams[2] = (DWORD)wReturnLength;
                break;

            case MCI_INTEGER:
                // The return value is an integer, flag to convert it
                // to a string later
                wConvertReturnValue = MCI_INTEGER;
                break;

            case MCI_RECT:
                // The return value is an rect, flag to
                // convert it to a string later
                wConvertReturnValue = MCI_RECT;
                break;
#ifdef DEBUG
            default:
                DOUT ("mciSendStringInternal:  Unknown return type\r\n");
                break;
#endif
        }
    }

    // We don't need this around anymore
    mciUnlockCommandTable (wTable);
    wTable = (UINT)-1;

    /* Fill the callback entry */
    lpdwParams[0] = (DWORD)(UINT)hwndCallback;

    // Kludge the type number for SYSINFO
    if (wMessage == MCI_SYSINFO)
        ((LPMCI_SYSINFO_PARMS)lpdwParams)->wDeviceType = mciLookUpType(lpstrDeviceName);

    // Now we actually send the command further into the bowels of MCI!

    // The INTERNAL version of mciSendCommand is used in order to get
    // special return description information encoded in the high word
    // of the return value and to get back the list of pointers allocated
    // by any parsing done in the open command
    {
        MCI_INTERNAL_OPEN_INFO OpenInfo;
        OpenInfo.lpstrParams = (LPSTR)lpstrCommand;
        OpenInfo.lpstrPointerList = lpstrPointerList;
        OpenInfo.hCallingTask = hCallingTask;
        OpenInfo.wParsingError = wParsingError;
        dwErr = mciSendCommandInternal (wDeviceID, wMessage, dwFlags,
                                        (DWORD)(LPDWORD)lpdwParams,
                                        &OpenInfo);
        // If the command was reparsed there may be a new pointer list
        // and the old one was free'd
        lpstrPointerList = OpenInfo.lpstrPointerList;
    }

    wErr = (UINT)dwErr;

    if (wErr != 0)
        // If command execution error
        goto cleanup;

    // Command executed OK
    // See if a string return came back with an integer instead
    if (dwErr & MCI_INTEGER_RETURNED)
        wConvertReturnValue = MCI_INTEGER;

    // If the return value must be converted
    if (wConvertReturnValue != 0 && wReturnLength != 0)
        wErr = mciConvertReturnValue (wConvertReturnValue, HIWORD(dwErr),
                                      wDeviceID, lpdwParams,
                                      lpstrReturnString, wReturnLength);

cleanup:
    if (wTable != -1)
        mciUnlockCommandTable (wTable);

    mciFree(lpstrCommandName);
    mciFree(lpstrDeviceName);
    if (lpdwParams != NULL)
        mciFree (lpdwParams);

    // Free any memory used by string parameters
    mciParserFree (lpstrPointerList);

    dwReturn =  (wErr >= MCIERR_CUSTOM_DRIVER_BASE ?
                (DWORD)wErr | (DWORD)wDeviceID << 16 :
                (DWORD)wErr);

#ifdef DEBUG
    if (dwReturn != 0)
    {
        char strTemp[MAXERRORLENGTH];

        if (!mciGetErrorString (dwReturn, strTemp, sizeof(strTemp)))
            LoadString(ghInst, STR_MCISSERRTXT, strTemp, sizeof(strTemp));
        else
            DPRINTF(("mciSendString: %ls\r\n",(LPSTR)strTemp));
    }
#endif

exitfn:
    if (lpstrInputCopy != NULL)
        mciFree (lpstrInputCopy);

#ifdef DEBUG
    mciCheckLocks();
#endif

    return dwReturn;
}

/*
 * @doc EXTERNAL MCI
 *
 * @api DWORD | mciSendString | This function sends a command string to an
 *  MCI device.  The device that the command is sent to is specified in the
 *  command string.
 *
 * @parm LPCSTR | lpstrCommand | Specifies an MCI command string.
 *
 * @parm LPSTR | lpstrReturnString | Specifies a buffer for return
 *  information. If no return information is needed, you can specify
 *  NUL for this parameter.
 *
 * @parm UINT | wReturnLength | Specifies the size of the return buffer
 *  specified by <p lpstrReturnString>.
 *
 * @parm HWND | hwndCallback | Specifies a handle to a window to call back
 *  if "notify" was specified in the command string.
 *
 * @rdesc Returns zero if the function was successful.  Otherwise, it returns
 *  error information. The low-order word
 *  of the returned DWORD contains the error return value.
 *
 *  To get a textual description of <f mciSendString> return values,
 *  pass the return value to <f mciGetErrorString>.
 *
 *  The error returns listed for <f mciSendCommand> also apply to
 *  <f mciSendString>. The following error returns are unique to
 *  <f mciSendString>:
 *
 *  @flag MCIERR_BAD_CONSTANT | Unknown value for parameter.
 *
 *  @flag MCIERR_BAD_INTEGER | Invalid or missing integer in command.
 *
 *  @flag MCIERR_DUPLICATE_FLAGS | A flag or value was specified twice.
 *
 *  @flag MCIERR_MISSING_COMMAND_STRING | No command was specified.
 *
 *  @flag MCIERR_MISSING_DEVICE_NAME | No device name was specified.
 *
 *  @flag MCIERR_MISSING_STRING_ARGUMENT | A string value was
 *  missing from the command.
 *
 *  @flag MCIERR_NEW_REQUIRES_ALIAS | An alias must be used
 *  with the "new" device name.
 *
 *  @flag MCIERR_NO_CLOSING_QUOTE | A closing quotation mark is missing.
 *
 *  @flag MCIERR_NOTIFY_ON_AUTO_OPEN | The "notify" flag is illegal
 *  with auto-open.
 *
 *  @flag MCIERR_PARAM_OVERFLOW | The output string was not long enough.
 *
 *  @flag MCIERR_PARSER_INTERNAL | Internal parser error.
 *
 *  @flag MCIERR_UNRECOGNIZED_KEYWORD | Unknown command parameter.
 *
 * @xref mciGetErrorString mciSendCommand
 */
DWORD WINAPI
mciSendString(
    LPCSTR lpstrCommand,
    LPSTR lpstrReturnString,
    UINT wReturnLength,
    HWND hwndCallback
    )
{
    DWORD   dwErr32;
    DWORD   dwErr16 = MMSYSERR_NOERROR;
    LPSTR   lpstr;
    BOOL    fHaveAll = FALSE;

    // Initialize the 16-bit device list
    if (!MCI_bDeviceListInitialized && !mciInitDeviceList()) {
        return MCIERR_OUT_OF_MEMORY;
    }

    dwErr32 = mciMessage( THUNK_MCI_SENDSTRING, (DWORD)lpstrCommand,
                          (DWORD)lpstrReturnString, (DWORD)wReturnLength,
                          (DWORD)hwndCallback );

    /*
    ** Even if the string was processed correctly by the 32 bit side
    ** we might still have to pass it through to the 16 bit side if it
    ** contains the string " all\0" or " all ".
    */
    lpstr = _fstrstr( lpstrCommand, " all" );
    if ( lpstr ) {

        lpstr += 4;

        if ( *lpstr == ' ' || *lpstr == '\0' ) {
            fHaveAll = TRUE;
        }
    }


    /*
    ** If we have the all device or an error from the 32 bit side
    ** we have to try the 16 bit side.
    */

    if ( !fHaveAll && dwErr32 == MMSYSERR_NOERROR ) {
        return dwErr32;
    }
    else {

        dwErr16 = mciSendStringInternal( lpstrCommand, lpstrReturnString,
                                         wReturnLength, hwndCallback, NULL );
    }


    /*
    ** Special processing of the return code is required if the
    ** MCI_ALL_DEVICE_ID was specified.
    */
    if ( fHaveAll ) {
        if ( (dwErr16 != MMSYSERR_NOERROR)
          && (dwErr32 != MMSYSERR_NOERROR) ) {

            return dwErr32;
        }
        else {
            return MMSYSERR_NOERROR;
        }
    }

    if ( dwErr32 != MCIERR_INVALID_DEVICE_NAME
      && dwErr16 != MMSYSERR_NOERROR ) {

         return dwErr32;
    }

    return dwErr16;
}


/*
 * @doc INTERNAL MCI
 *
 * @api BOOL | mciExecute | This function is a simplified version of the
 *  <f mciSendString> function. It does not take a buffer for
 *  return information, and it displays a message box when errors occur.
 *
 * @parm LPCSTR | lpstrCommand | Specifies an MCI command string.
 *
 * @rdesc TRUE if successful, FALSE if unsuccessful.
 *
 * @comm This function provides a simple interface to MCI from scripting
 *  languages.
 *
 * @xref mciSendString
 */
BOOL WINAPI
mciExecute(
    LPCSTR lpstrCommand
    )
{
    char aszError[MAXERRORLENGTH];
    DWORD dwErr;
    LPSTR lpstrName;

    if (LOWORD(dwErr = mciSendString (lpstrCommand, NULL, 0, NULL)) == 0)
        return TRUE;

    if (!mciGetErrorString (dwErr, aszError, sizeof(aszError)))
        LoadString(ghInst, STR_MCIUNKNOWN, aszError, sizeof(aszError));
    else

    if (lpstrCommand != NULL)
    {
// Skip initial blanks
        while (*lpstrCommand == ' ')
            ++lpstrCommand;
// Then skip the command
        while (*lpstrCommand != ' ' && *lpstrCommand != '\0')
            ++lpstrCommand;
// Then blanks before the device name
        while (*lpstrCommand == ' ')
            ++lpstrCommand;

// Now, get the device name
        if (lpstrCommand != '\0' &&
            mciEatToken (&lpstrCommand, ' ', &lpstrName, FALSE) != 0)
            DOUT ("Could not allocate device name text for error box\r\n");
    } else
        lpstrName = NULL;

    MessageBox (NULL, aszError, lpstrName, MB_ICONHAND | MB_OK);

    if (lpstrName != NULL)
        mciFree(lpstrName);

    return FALSE;
}

/*
 * @doc EXTERNAL MCI
 *
 * @api BOOL | mciGetErrorString | This function returns a
 * textual description of the specified MCI error.
 *
 * @parm DWORD | dwError | Specifies the error code returned by
 *  <f mciSendCommand> or <f mciSendString>.
 *
 * @parm LPSTR | lpstrBuffer | Specifies a pointer to a buffer that is
 *  filled with a textual description of the specified error.
 *
 * @parm UINT | wLength | Specifies the length of the buffer pointed to by
 *  <p lpstrBuffer>.
 *
 * @rdesc Returns TRUE if successful.  Otherwise, the given error code
 *  was not known.
 */
BOOL WINAPI
mciGetErrorString (
    DWORD dwError,
    LPSTR lpstrBuffer,
    UINT wLength
    )
{
    HINSTANCE hInst;


    if (lpstrBuffer == NULL)
        return FALSE;

    if ( mciMessage( THUNK_MCI_GETERRORSTRING, (DWORD)dwError,
                     (DWORD)lpstrBuffer, (DWORD)wLength, 0L ) ) {
        return TRUE;
    }

// If the high bit is set then get the error string from the driver
// else get it from mmsystem.dll
    if (HIWORD(dwError) != 0)
    {
        if (!MCI_VALID_DEVICE_ID (HIWORD (dwError)) || !(hInst = MCI_lpDeviceList[HIWORD (dwError)]->hDriver))
        {
            hInst = ghInst;
            dwError = MCIERR_DRIVER;
        }
    } else
        hInst = ghInst;

    if (LoadString (hInst, LOWORD(dwError), lpstrBuffer, wLength) == 0)
    {
// If the string load failed then at least terminate the string
        if (wLength > 0)
            *lpstrBuffer = '\0';
        return FALSE;
    }
    else
        return TRUE;
}

#if 0
/* Return non-zero if load successful */
BOOL NEAR PASCAL MCIInit(void)
{
    mci32Message = (LPMCIMESSAGE)GetProcAddress32W( mmwow32Lib,
                                                    "mci32Message" );
    return TRUE;
}
#endif


void NEAR PASCAL
MCITerminate(
    void
    )
{
/*
    We would like to close all open devices here but cannot because of
 	   unknown WEP order
*/
    if (hMciHeap != NULL)
        HeapDestroy(hMciHeap);

    hMciHeap = NULL;
}