/*******************************Module*Header*********************************\
* Module Name: mciparse.c
*
* Media Control Architecture Command Parser
*
* Created: 3/2/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"

#ifndef STATICFN
#define STATICFN
#endif

#define	WCODE	UINT _based(_segname("_CODE"))

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

#ifdef DEBUG_RETAIL
extern int DebugmciSendCommand;
#endif

// Number of command tables registered, including "holes"
static UINT number_of_command_tables;

// Command table list
command_table_type command_tables[MAX_COMMAND_TABLES];

static SZCODE szTypeTableExtension[] = ".mci";
static SZCODE szCoreTable[] = "core";

// Core table is loaded when the first MCI command table is requested
static BOOL bCoreTableLoaded;

// One element for each device type.  Value is the table type to use
// or 0 if there is no device type specific table.
static WCODE table_types[] =
{
    MCI_DEVTYPE_VCR,                // vcr
    MCI_DEVTYPE_VIDEODISC,          // videodisc
    MCI_DEVTYPE_OVERLAY,            // overlay
    MCI_DEVTYPE_CD_AUDIO,           // cdaudio
    MCI_DEVTYPE_DAT,                // dat
    MCI_DEVTYPE_SCANNER,            // scanner
    MCI_DEVTYPE_ANIMATION,          // animation
    MCI_DEVTYPE_DIGITAL_VIDEO,      // digitalvideo
    MCI_DEVTYPE_OTHER,              // other
    MCI_DEVTYPE_WAVEFORM_AUDIO,     // waveaudio
    MCI_DEVTYPE_SEQUENCER           // sequencer
};
/*!!
void PASCAL NEAR mciToLower (LPSTR lpstrString)
{
    while (*lpstrString != '\0')
    {
        if (*lpstrString >= 'A' && *lpstrString <= 'Z')
            *lpstrString += 0x20;

        ++lpstrString;
    }
}
*/
/*
 * @doc INTERNAL MCI
 * @func UINT | mciEatCommandEntry | Read a command resource entry and
 * return its length and its value and identifier
 *
 * @parm LPCSTR | lpEntry | The start of the command resource entry
 *
 * @parm LPDWORD | lpValue | The value of the entry, returned to caller
 * May be NULL
 *
 * @parm UINT FAR* | lpID | The identifier of the entry, returned to caller
 * May be NULL
 *
 * @rdesc The total number of bytes in the entry
 *
 */
UINT PASCAL NEAR mciEatCommandEntry (
LPCSTR lpEntry,
LPDWORD lpValue,
UINT FAR* lpID)
{
    LPCSTR lpScan = lpEntry;

    while (*lpScan++ != '\0')
        ;
    if (lpValue != NULL)
        *lpValue = *(LPDWORD)lpScan;
    lpScan += sizeof(DWORD);
    if (lpID != NULL)
        *lpID = *(LPWORD)lpScan;
    lpScan += sizeof(UINT);

    return lpScan - lpEntry;
}

// Return the size used by this token in the parameter list
UINT PASCAL NEAR mciGetParamSize (DWORD dwValue, UINT wID)
{
    switch (wID)
    {
        case MCI_CONSTANT:
        case MCI_INTEGER:
        case MCI_STRING:
            return sizeof(DWORD);
        case MCI_RECT:
            return sizeof(RECT);
        case MCI_RETURN:
            switch ((UINT)dwValue)
            {
                case MCI_INTEGER:
                    return sizeof(DWORD);
                case MCI_STRING:
                case MCI_RECT:
                    return sizeof(RECT);
                default:
                    DOUT ("mciGetParamSize:  Unknown return type\r\n");
                    return 0;
            }
            break;
    }
    return 0;
}


/*
 * @doc INTERNAL MCI
 * @func UINT | mciRegisterCommandTable | This function adds a new
 * table for the MCI parser.
 *
 * @parm HGLOBAL | hResource | Handle to the RCDATA resource
 *
 * @parm UINT FAR* | lpwIndex | Pointer to command table index
 *
 * @parm UINT   | wType | Specifies the device type for this command table.
 * Driver tables and the core table are type 0.
 *
 * @parm HINSTANCE | hModule | Module instance registering table.
 *
 * @rdesc Returns the command table index number that was assigned or -1
 * on error.
 *
 */
STATICFN UINT PASCAL NEAR
mciRegisterCommandTable(
    HGLOBAL hResource,
    UINT FAR* lpwIndex,
    UINT wType,
    HINSTANCE hModule
    )
{
    UINT wID;

/* First check for free slots */
    for (wID = 0; wID < number_of_command_tables; ++wID)
        if (command_tables[wID].hResource == NULL)
            break;

/* If no empty slots then allocate another one */
    if (wID >= number_of_command_tables)
    {
        if (number_of_command_tables == MAX_COMMAND_TABLES)
        {
            DOUT ("mciRegisterCommandTable: No more tables\r\n");
            return -1;
        }
        else
           wID = number_of_command_tables++;
    }

/* Fill in the slot */
    command_tables[wID].wType = wType;
    command_tables[wID].hResource = hResource;
    command_tables[wID].lpwIndex = lpwIndex;
    command_tables[wID].hModule = hModule;
#ifdef DEBUG
    command_tables[wID].wLockCount = 0;
#endif
#ifdef DEBUG
    if (DebugmciSendCommand > 2)
    {
        DPRINTF(("mciRegisterCommandTable INFO: assigned slot %u\r\n", wID));
        DPRINTF(("mciRegisterCommandTable INFO: #tables is %u\r\n",
                 number_of_command_tables));
    }
#endif
    return wID;
}

/*
 * @doc DDK MCI
 * @api UINT | mciLoadCommandResource | Registers the indicated
 * resource as an MCI command table and builds a command table
 * index.  If a file with the resource name and the extension '.mci' is
 * found in the path then the resource is taken from that file.
 *
 * @parm HINSTANCE | hInstance | The instance of the module whose executable
 * file contains the resource.  This parameter is ignored if an external file
 * is found.
 *
 * @parm LPCSTR | lpResName | The name of the resource
 *
 * @parm UINT | wType | The table type.  Custom device specific tables MUST
 * give a table type of 0.
 *
 * @rdesc Returns the command table index number that was assigned or -1
 * on error.
 *
 */
UINT WINAPI mciLoadCommandResource (
HINSTANCE hInstance,
LPCSTR lpResName,
UINT wType)
{
    UINT FAR*           lpwIndex, FAR* lpwScan;
    HINSTANCE           hExternal;
    HRSRC               hResInfo;
    HGLOBAL             hResource;
    LPSTR               lpResource, lpScan;
    int                 nCommands = 0;
    UINT                wID;
    UINT                wLen;
                        // Name + '.' + Extension + '\0'
    char                strFile[8 + 1 + 3 + 1];
    LPSTR               lpstrFile = strFile;
    LPCSTR              lpstrType = lpResName;
    OFSTRUCT            ofs;

#ifdef DEBUG
    if (DebugmciSendCommand > 2)
        DPRINTF(("mciLoadCommandResource INFO:  %s loading\r\n", (LPSTR)lpResName));
#endif

// Initialize the device list
    if (!MCI_bDeviceListInitialized && !mciInitDeviceList())
        return MCIERR_OUT_OF_MEMORY;

// Load the core table if its not already there
    if (!bCoreTableLoaded)
    {
        bCoreTableLoaded = TRUE;
// If its not the core table being loaded
        if (lstrcmpi (szCoreTable, lpResName) != 0)
            if (mciLoadCommandResource (ghInst, szCoreTable, 0) == -1)
            {
                DOUT ("mciLoadCommandResource:  Cannot load core table\r\n");
            }
    }

// Check for a file with the extension ".mci"
// Copy up to the first eight characters of device type
    while (lpstrType < lpResName + 8 && *lpstrType != '\0')
        *lpstrFile++ = *lpstrType++;

// Tack extension onto end
    lstrcpy (lpstrFile, szTypeTableExtension);

// If the file exists and can be loaded then set flag
// otherwise load resource from MMSYSTEM.DLL
    if (OpenFile (strFile, &ofs, OF_EXIST) == HFILE_ERROR ||
        (hExternal = LoadLibrary(strFile)) < HINSTANCE_ERROR)

        hExternal = NULL;

// Load the given table from the file or from the module if not found
    if (hExternal != NULL &&
        (hResInfo = FindResource (hExternal, lpResName, RT_RCDATA)) != NULL)

        hInstance = hExternal;
    else
        hResInfo = FindResource (hInstance, lpResName, RT_RCDATA);

    if (hResInfo == NULL)
    {
        DOUT ("mciLoadCommandResource:  Cannot find command resource\r\n");
        return -1;
    }
    if ((hResource = LoadResource (hInstance, hResInfo)) == NULL)
    {
        DOUT ("mciLoadCommandResource:  Cannot load command resource\r\n");
        return -1;
    }
    if ((lpResource = LockResource (hResource)) == NULL)
    {
        DOUT ("mciLoadCommandResource:  Cannot lock resource\r\n");
        FreeResource (hResource);
        return -1;
    }

/* Count the number of commands  */
    lpScan = lpResource;
    while (TRUE)
    {
        lpScan += mciEatCommandEntry(lpScan, NULL, &wID);

// End of command?
        if (wID == MCI_COMMAND_HEAD)
            ++nCommands;
// End of command list?
        else if (wID == MCI_END_COMMAND_LIST)
            break;
    }

// There must be at least on command in the table
    if (nCommands == 0)
    {
        DOUT ("mciLoadCommandResource:  No commands in the specified table\r\n");
        UnlockResource (hResource);
        FreeResource (hResource);
        return -1;
    }

// Allocate storage for the command table index
// Leave room for a -1 entry to terminate it
    if ((lpwIndex = (UINT FAR*)
                        mciAlloc ((UINT)sizeof (UINT) * (nCommands + 1)))
                        == NULL)
    {
        DOUT ("mciLoadCommandResource:  cannot allocate command table index\r\n");
        UnlockResource (hResource);
        FreeResource (hResource);
        return -1;
    }

/* Build Command Table */
    lpwScan = lpwIndex;
    lpScan = lpResource;

    while (TRUE)
    {
// Get next command entry
        wLen = mciEatCommandEntry (lpScan, NULL, &wID);

        if (wID == MCI_COMMAND_HEAD)
// Add an index to this command
            *lpwScan++ = lpScan - lpResource;

        else if (wID == MCI_END_COMMAND_LIST)
        {
// Mark the end of the table
            *lpwScan = -1;
            break;
        }
        lpScan += wLen;
    }
    UnlockResource (hResource);
    return mciRegisterCommandTable (hResource, lpwIndex, wType, hExternal);
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciLoadTableType | If the table of the given type
 * has not been loaded, register it
 *
 * @parm UINT | wType | The table type to load
 *
 * @rdesc Returns the command table index number that was assigned or -1
 * on error.
 */
UINT PASCAL NEAR mciLoadTableType (
UINT wType)
{
    UINT wID;
    char buf[MCI_MAX_DEVICE_TYPE_LENGTH];
    int nTypeLen;

// Check to see if this table type is already loaded
    for (wID = 0; wID < number_of_command_tables; ++wID)
        if (command_tables[wID].wType == wType)
            return wID;

// Must load table
// First look up what device type specific table to load for this type
    if (wType < MCI_DEVTYPE_FIRST || wType > MCI_DEVTYPE_LAST)
        return -1;

// Load string that corresponds to table type
    LoadString (ghInst, table_types[wType - MCI_DEVTYPE_FIRST],
                buf, sizeof(buf));

//Must be at least one character in type name
    if ((nTypeLen = lstrlen (buf)) < 1)
        return -1;

// Register the table with MCI
    return mciLoadCommandResource (ghInst, buf, wType);
}

/*
 * @doc DDK MCI
 *
 * @api BOOL | mciFreeCommandResource | Frees the memory used
 * by the specified command table.
 *
 * @parm UINT | wTable | The table index returned from a previous call to
 * mciLoadCommandResource.
 *
 * @rdesc FALSE if the table index is not valid, TRUE otherwise.
 *
 */
BOOL WINAPI mciFreeCommandResource (
UINT wTable)
{
    UINT wID;
    HGLOBAL hResource;
    UINT FAR* lpwIndex;

#ifdef DEBUG
    if (DebugmciSendCommand > 2)
    {
        DPRINTF(("mciFreeCommandResource INFO:  Free table %d\r\n", wTable));
        DPRINTF(("mciFreeCommandResource INFO:  Lockcount is %d\r\n",
                  command_tables[wTable].wLockCount));
    }
#endif

/* Validate input -- do not let the core table be free'd */
    if (wTable <= 0 || wTable >= number_of_command_tables)
    {
#ifdef DEBUG
// wTable == -1 is OK
        if (wTable != -1)
            DOUT ("mciFreeCommandResource: Bad table number\r\n");
#endif
        return FALSE;
    }

// If this table is being used elsewhere then keep it around
    for (wID = 1; wID < MCI_wNextDeviceID; ++wID)
        if (MCI_lpDeviceList[wID] != NULL)
            if (MCI_lpDeviceList[wID]->wCustomCommandTable == wTable ||
                MCI_lpDeviceList[wID]->wCommandTable == wTable)
            {
#ifdef DEBUG
                if (DebugmciSendCommand > 2)
                    DOUT ("mciFreeCommandResource INFO:  table in use\r\n");
#endif
                return FALSE;
            }

#if 0
/* Search the list of tables */
    for (wID = 0; wID < number_of_command_tables; ++wID)

/* If this resource is still in use, keep it around */
        if (command_tables[wID].hResource == hResource)
        {
#ifdef DEBUG
            if (DebugmciSendCommand > 2)
                DOUT ("mciFreeCommandResource INFO:  resource in use\r\n");
#endif
            return FALSE;
        }
#endif

    hResource = command_tables[wTable].hResource;
    command_tables[wTable].hResource = NULL;

    lpwIndex = command_tables[wTable].lpwIndex;
    command_tables[wTable].lpwIndex = NULL;
    command_tables[wTable].wType = 0;


    FreeResource (hResource);
    mciFree (lpwIndex);

    if (command_tables[wTable].hModule != NULL)
        FreeLibrary (command_tables[wTable].hModule);

// Make space at top of list
    if (wTable == number_of_command_tables - 1)
        --number_of_command_tables;

#ifdef DEBUG
    if (DebugmciSendCommand > 2)
        DPRINTF(("mciFreeCommandResource INFO:  number_of_command_tables: %u\r\n",
                                             number_of_command_tables));
#endif

    return TRUE;
}

#ifdef DEBUG
void PASCAL NEAR mciCheckLocks (void)
{
    UINT wTable;

    if (DebugmciSendCommand <= 2)
        return;

    for (wTable = 0; wTable < number_of_command_tables; ++wTable)
    {
        if (command_tables[wTable].hResource == NULL)
            continue;
        DPRINTF(("mciCheckLocks INFO: table %u ", wTable));
        DPRINTF(("user: %x ",
                 GlobalFlags (command_tables[wTable].hResource) & GMEM_LOCKCOUNT));
        DPRINTF(("mci: %u ", command_tables[wTable].wLockCount));
        if (GlobalFlags (command_tables[wTable].hResource) & GMEM_DISCARDABLE)
            DPRINTF(("discardable\r\n"));
        else
            DPRINTF(("NOT discardable\r\n"));
    }
}
#endif

/*
 * @doc INTERNAL MCI
 * @func BOOL | mciUnlockCommandTable | Unlocks the command table given by
 * a table index
 *
 * @parm UINT | wCommandTable | Table to unlock
 *
 * @rdesc TRUE if success, FALSE otherwise
 *
 * @comm Used external to this module by mci.c
 *
 */
BOOL PASCAL NEAR mciUnlockCommandTable (
UINT wCommandTable)
{
    UnlockResource(command_tables[wCommandTable].hResource);
#ifdef DEBUG
    --command_tables[wCommandTable].wLockCount;
    if (DebugmciSendCommand > 2)
    {
        DPRINTF(("mciUnlockCommandTable INFO:  table %d\r\n", wCommandTable));
        DOUT ("mciUnlockCommandTable INFO:  check locks...\r\n");
        mciCheckLocks();
    }
#endif
    return TRUE;
}

/*
 * @doc INTERNAL MCI
 * @func LPSTR | FindCommandInTable | Look up the given
 * command string in the GIVEN parser command table
 *
 * @parm UINT  | wTable | Command table to use
 *
 * @parm LPCSTR | lpstrCommand | The command to look up.  It must
 * be in lower case with no leading or trailing blanks and with at
 * least one character.
 *
 * @parm UINT FAR * | lpwMessage | The message corresponding to the command
 * Returned to caller.
 *
 * @rdesc NULL if the command is unknown or on error, otherwise a pointer to
 * the command list for the input command string.
 *
 * @comm If the command is found, the command resource will be locked on exit.
 *
 */
LPSTR PASCAL NEAR FindCommandInTable (
UINT wTable,
LPCSTR lpstrCommand,
UINT FAR * lpwMessage)
{
    UINT FAR* lpwIndex;
    LPSTR lpResource, lpstrThisCommand;
    UINT  wMessage;

/* Validate table */

    if (wTable >= number_of_command_tables)
    {
// Check the core table but its not yet loaded
        if (wTable == 0)
        {
// Try to load it
            if (mciLoadCommandResource (ghInst, szCoreTable, 0) == -1)
            {
                DOUT ("FindCommandInTable:  cannot load core table\r\n");
                return NULL;
            }
        } else
        {
            DOUT ("MCI FindCommandInTable:  invalid table ID\r\n");
            return NULL;
        }

    }

    if ((lpResource = LockResource (command_tables[wTable].hResource)) == NULL)
    {
        DOUT ("MCI FindCommandInTable:  Cannot lock table resource\r\n");
        return NULL;
    }
#ifdef DEBUG
    ++command_tables[wTable].wLockCount;
#endif

// Look at each command in the table
    lpwIndex = command_tables[wTable].lpwIndex;
    if (lpwIndex == NULL)
    {
        DOUT ("MCI FindCommandInTable:  null command table index\r\n");
        return NULL;
    }

    while (*lpwIndex != -1)
    {
        DWORD dwMessage;

        lpstrThisCommand = *lpwIndex++ + lpResource;

// Get message number from the table
        mciEatCommandEntry (lpstrThisCommand, &dwMessage, NULL);
        wMessage = (UINT)dwMessage;

// Does this command match the input?

// String case
        if  (HIWORD  (lpstrCommand) != 0 &&
             lstrcmpi (lpstrThisCommand, lpstrCommand) == 0  ||

// Message case
             HIWORD (lpstrCommand) == 0 &&
             wMessage == LOWORD ((DWORD)lpstrCommand))
        {
// Retain the locked resource pointer
                command_tables[wTable].lpResource = lpResource;

// Address the message ID which comes after the command name
                if (lpwMessage != NULL)
                    *lpwMessage = wMessage;
// Leave table locked on exit
                return lpstrThisCommand;
        }

// Strings don't match, go to the next command in the table
    }

    UnlockResource (command_tables[wTable].hResource);
#ifdef DEBUG
    --command_tables[wTable].wLockCount;
#endif

    return NULL;
}

/*
 * @doc INTERNAL MCI
 * @func LPSTR | FindCommandItem | Look up the given
 * command string in the parser command tables
 *
 * @parm UINT | wDeviceID | The device ID used for this command.
 * If 0 then only the system core command table is searched.
 *
 * @parm LPCSTR | lpstrType | The type name of the device
 *
 * @parm LPCSTR | lpstrCommand | The command to look up.  It must
 * be in lower case with no leading or trailing blanks and with at
 * least one character.  If the HIWORD is 0 then the LOWORD contains
 * the command message ID instead of a command name and the function is
 * merely to find the command list pointer.
 *
 * If the high word is 0 then the low word is an command ID value instead
 * of a command name
 *
 * @parm UINT FAR* | lpwMessage | The message corresponding to the command
 * Returned to caller.
 *
 * @parm UINT FAR* | lpwTable | The table index in which the command was found
 * Returned to caller.
 *
 * @rdesc NULL if the command is unknown, otherwise a pointer to
 * the command list for the input command string.
 */
LPSTR PASCAL NEAR FindCommandItem (
UINT wDeviceID,
LPCSTR lpstrType,
LPCSTR lpstrCommand,
UINT FAR* lpwMessage,
UINT FAR* lpwTable)
{
    LPSTR lpCommand;
    UINT wTable;
    LPMCI_DEVICE_NODE nodeWorking;

// Only check hiword per comments above
    if (HIWORD (lpstrCommand) != NULL && *lpstrCommand == '\0')
    {
        DOUT ("MCI FindCommandItem:  lpstrCommand is NULL or empty string\r\n");
        return NULL;
    }

// If a specific device ID was specified then look in any custom table
// or type table
    if (wDeviceID != 0 && wDeviceID != MCI_ALL_DEVICE_ID)
    {
// If the device ID is valid
        if (!MCI_VALID_DEVICE_ID(wDeviceID))
        {
            DOUT ("MCI FindCommandItem:  Invalid device ID\r\n");
            return NULL;
        }
        nodeWorking = MCI_lpDeviceList[wDeviceID];

// If there is a custom command table then use it
        if ((wTable = nodeWorking->wCustomCommandTable) != -1)
        {
            lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
            if (lpCommand != NULL)
                goto exit;
        }
// Get the device type table from the existing device
// Relies on mciReparseCommand in mciLoadDevice to catch all device type
// tables when device is not yet open.
        if ((wTable = nodeWorking->wCommandTable) != -1)
        {
            lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
            if (lpCommand != NULL)
                goto exit;
        }
    }

// If no match was found in the device or type specific tables
// Look in the core table
    wTable = 0;
    lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage);
    if (lpCommand == NULL)
        wTable = -1;

exit:
    if (lpwTable != NULL)
        *lpwTable = wTable;

#ifdef DEBUG
    if (DebugmciSendCommand > 2)
    {
        DOUT ("FindCommandItem INFO:  check locks...\r\n");
        mciCheckLocks();
    }
#endif

    return lpCommand;
}

/*
 * @doc INTERNAL MCI
 * @func LPSTR | mciCheckToken | Check to see if the command item matches
 * the given string, allowing multiple blanks in the input parameter to
 * match a corresponding single blank in the command token and ignoring
 * case.
 *
 * @parm LPCSTR | lpstrToken | The command token to check
 *
 * @parm LPCSTR | lpstrParam | The input parameter
 *
 * @rdesc NULL if no match, otherwise points to the first character
 * after the parameter
 *
 */
STATICFN LPSTR PASCAL NEAR
mciCheckToken(
    LPCSTR lpstrToken,
    LPCSTR lpstrParam
    )
{
/* Check for legal input */
    if (lpstrToken == NULL || lpstrParam == NULL)
        return NULL;

    while (*lpstrToken != '\0' && MCI_TOLOWER(*lpstrParam) == *lpstrToken)
    {
// If the token contains a blank, allow more than one blank in the
// parameter
        if (*lpstrToken == ' ')
            while (*lpstrParam == ' ')
                ++lpstrParam;
        else
            *lpstrParam++;
        *lpstrToken++;
    }
    if (*lpstrToken != '\0'|| (*lpstrParam != '\0' && *lpstrParam != ' '))
        return NULL;
    else
        return (LPSTR)lpstrParam;
}

/*
 * @doc INTERNAL MCI
 * @api BOOL | mciParseInteger | Parse the given integer
 *
 * @parm LPSTR FAR * | lplpstrInput | The string containing the argument.
 * It is updated and returned to the caller pointing to the first character
 * after the argument or to the first character that is in error.
 *
 * @parm LPDWORD | lpdwArgument | The place to put the output
 *
 * @rdesc Returns TRUE if not error
 *
 * @comm If there are colons in the input (':') the result is "colonized".
 * This means that each time a colon is read, the current result is written
 * and any subsequent digits are shifted left one byte.  No one "segment"
 * can be more than 0xFF.  For example, "0:1:2:3" is parsed to 0x03020100.
 *
 */
STATICFN BOOL PASCAL NEAR
mciParseInteger(
    LPSTR FAR * lplpstrInput,
    LPDWORD lpdwArgument
    )
{
    LPSTR lpstrInput = *lplpstrInput;
    BOOL fDigitFound;
    DWORD dwResult;
    LPSTR lpstrResult = (LPSTR)lpdwArgument;
    int   nDigitPosition = 0;
    BOOL  bSigned = FALSE;

// Leading blanks have been removed by mciParseParams

    if (*lpstrInput == '-')
    {
        ++lpstrInput;
        bSigned = TRUE;
    }

// Read digits
    *lpdwArgument = 0;                      /* Initialize */
    dwResult = 0;
    fDigitFound = FALSE;                    /* Initialize */
    while (*lpstrInput >= '0' && *lpstrInput <= '9' || *lpstrInput == ':')
    {
// ':' indicates colonized data
        if (*lpstrInput == ':')
        {
// Cannot mix colonized and signed forms
            if (bSigned)
            {
                DOUT ("Bad integer: mixing signed and colonized forms\r\n");
                return FALSE;
            }
// Check for overflow in accumulated colonized byte
            if (dwResult > 0xFF)
                return FALSE;

// Copy and move to next byte converted in output
            *lpstrResult++ = (char)dwResult;
            ++lpstrInput;
// Initialize next colonized byte
            dwResult = 0;
            ++nDigitPosition;
// Only allow four colonized components
            if (nDigitPosition > 3)
            {
                DOUT ("Bad integer:  Too many colonized components\r\n");
                return FALSE;
            }
        } else
        {
            char cDigit = (char)(*lpstrInput++ - '0');
// Satisfies condition that at least one digit must be read
            fDigitFound = TRUE;

            if (dwResult > 0xFFFFFFFF / 10)
// Overflow if multiply was to occur
                return FALSE;
            else
// Multiply for next digit
                dwResult *= 10;

// Add new digit
            dwResult += cDigit;
        }
    }
    if (nDigitPosition == 0)
    {
// No colonized components
        if (bSigned)
        {
// Check for overflow from negation
            if (dwResult > 0x7FFFFFFF)
                return FALSE;
// Negate result because a '-' sign was parsed
            dwResult = -dwResult;
        }

        *lpdwArgument = dwResult;
    }
    else
// Store last colonized component
    {
// Check for overflow
        if (dwResult > 0xFF)
            return FALSE;
// Store component
        *lpstrResult = (char)dwResult;
    }

    *lplpstrInput = lpstrInput;

/*
If there were no digits or if the digits were followed by a character
other than a blank or a '\0', then return a syntax error.
*/
    return fDigitFound && (!*lpstrInput || *lpstrInput == ' ');
}

/*
 * @doc INTERNAL MCI
 * @func BOOL | mciParseConstant | Parse the given integer
 *
 * @parm LPSTR FAR * | lplpstrInput | The string containing the argument.
 * It is updated and returned to the caller pointing to the first character
 * after the argument or to the first character that is in error.
 *
 * @parm LPDWORD | lpdwArgument | The place to put the output
 *
 * @parm LPSTR | lpItem | Pointer into command table.
 *
 * @rdesc Returns TRUE if not error
 *
 */
STATICFN BOOL PASCAL NEAR
mciParseConstant(
    LPSTR FAR * lplpstrInput,
    LPDWORD lpdwArgument,
    LPSTR lpItem
    )
{
    LPSTR lpPrev;
    DWORD dwValue;
    UINT wID;

// Skip past constant header
    lpItem += mciEatCommandEntry (lpItem, &dwValue, &wID);

    while (TRUE)
    {
        LPSTR lpstrAfter;

        lpPrev = lpItem;

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

        if (wID == MCI_END_CONSTANT)
            break;

        if ((lpstrAfter = mciCheckToken (lpPrev, *lplpstrInput)) != NULL)
        {
            *lpdwArgument = dwValue;
            *lplpstrInput = lpstrAfter;
            return TRUE;
        }

    }

    return mciParseInteger (lplpstrInput, lpdwArgument);
}

/*
 * @doc INTERNAL MCI
 * @func UINT | mciParseArgument | Parse the given argument
 *
 * @parm DWORD | dwValue | The argument value
 *
 * @parm UINT | wID | The argument ID
 *
 * @parm LPSTR FAR * | lplpstrOutput | The string containing the argument.
 * It is updated and returned to the caller pointing to the first character
 * after the argument or to the first character that is in error.
 *
 * @parm LPDWORD | lpdwFlags | The output flags
 *
 * @parm LPDWORD | lpArgument | The place to put the output
 *
 * @rdesc Returns 0 if no error or
 * @flag MCIERR_BAD_INTEGER | An integer argument could not be parsed
 * @flag MCIERR_MISSING_STRING_ARGUMENT | An expected string argument
 * @flag MCIERR_PARM_OVERFLOW | The output buffer was a NULL pointer
 * was missing
 *
 */
STATICFN UINT PASCAL NEAR
mciParseArgument(
    DWORD dwValue,
    UINT wID,
    LPSTR FAR * lplpstrOutput,
    LPDWORD lpdwFlags,
    LPSTR lpArgument,
    LPSTR lpCurrentCommandItem
    )
{
LPSTR lpstrInput =  *lplpstrOutput;
UINT wRetval = 0;

/* Switch on the argument type */
    switch (wID)
    {

// The parameter is a flag
        case MCI_FLAG:
            break; /* switch */

        case MCI_CONSTANT:
            if (*lpstrInput == '\0')
                wRetval = MCIERR_NO_INTEGER;
            else if (!mciParseConstant (&lpstrInput, (LPDWORD)lpArgument,
                                        lpCurrentCommandItem))
                wRetval = MCIERR_BAD_CONSTANT;
            break;

/* The parameter has an integer argument, try to parse it */
        case MCI_INTEGER:
            if (!mciParseInteger (&lpstrInput, (LPDWORD)lpArgument))
                wRetval = MCIERR_BAD_INTEGER;

            break; /* switch */
        case MCI_RECT:
        {
// Read in four RECT components.  Resulting structure is the
// same as a Windows RECT
            long lTemp;
            int n;
            for (n = 0; n < 4; ++n)
            {
                if (!mciParseInteger (&lpstrInput, &lTemp))
                {
                    wRetval = MCIERR_BAD_INTEGER;
                    break;
                }
// Each component is a signed 16 bit number
                if (lTemp > 32768 || lTemp < -32767)
                {
                    wRetval = MCIERR_BAD_INTEGER;
                    break;
                }
                ((int FAR *)lpArgument)[n] = (int)lTemp;
// Remove leading blanks before next digit
                while (*lpstrInput == ' ') ++lpstrInput;
            }
            break;
        }

        case MCI_STRING:
        {
            LPSTR lpstrOutput;

/* The parameter has an string argument, read it */

// Leading blanks have been removed by mciParseParams

/* Are there any non-blank characters left in the input? */
            if (*lpstrInput == '\0')
            {
/* Return an error */
                wRetval = MCIERR_MISSING_STRING_ARGUMENT;
                break; /* switch */
            }

            if ((wRetval = mciEatToken (&lpstrInput, ' ', &lpstrOutput, FALSE))
                != 0)
            {
                DOUT ("mciParseArgument:  error parsing string\r\n");
                return wRetval;
            }

            *(LPDWORD)lpArgument = (DWORD)lpstrOutput;

// NOTE:  mciSendString frees the output string after command execution
// by calling mciParserFree

            break; /* switch */
        } /* case */
    } /* switch */

/* Update the output flags if there was no error */
    if (wRetval == 0)
    {
        if (*lpdwFlags & dwValue)
        {
            if (wID == MCI_CONSTANT)
                wRetval = MCIERR_FLAGS_NOT_COMPATIBLE;
            else
                wRetval = MCIERR_DUPLICATE_FLAGS;
        } else
            *lpdwFlags |= dwValue;
    }
/*
   Return the input pointer pointing at the first character after
   the argument or to the first character that is in error
*/
    *lplpstrOutput = lpstrInput;
    return wRetval;
}

/*
 * @doc MCI INTERNAL
 * @func UINT | mciParseParams | Parse the command parameters
 *
 * @parm LPCSTR | lpstrParams | The parameter string
 *
 * @parm LPCSTR | lpCommandList | The command table description
 * of the command tokens
 *
 * @parm LPDWORD | lpdwFlags | Return the parsed flags here
 *
 * @parm LPDWORD | lpdwOutputParams | Return the list of parameters here
 *
 * @parm UINT | wParamsSize | The size allocated for the parameter list
 *
 * @parm LPSTR FAR * FAR * | lpPointerList | A NULL terminated list of
 * pointers allocated by this function that should be free'd when
 * no longer needed.   The list itself should be free'd also.  In both
 * cases, use mciFree().
 *
 * @parm UINT FAR* | lpwParsingError | If not NULL then if the command is
 * 'open', unrecognized keywords return an error here, and the
 * function return value is 0 (unless other errors occur).  This
 * is used to allow reparsing of the command by mciLoadDevice
 *
 * @rdesc Returns zero if successful or one of the following error codes:
 * @flag MCIERR_PARM_OVERFLOW | Not enough space for parameters
 * @flag MCIERR_UNRECOGNIZED_KEYWORD | Unrecognized keyword
 *
 * @comm Any syntax error, including missing arguments, will result in
 * a non-zero error return and invalid output data.
 *
 */
UINT PASCAL NEAR
mciParseParams(
    LPCSTR lpstrParams,
    LPCSTR lpCommandList,
    LPDWORD lpdwFlags,
    LPSTR lpOutputParams,
    UINT wParamsSize,
    LPSTR FAR * FAR *lpPointerList,
    UINT FAR* lpwOpenError
    )
{
    LPSTR lpFirstCommandItem, lpCurrentCommandItem;
    UINT wArgumentPosition, wErr, wLen, wID, wDefaultID;
    DWORD dwValue, dwDefaultValue;
    BOOL bOpenCommand;
    LPSTR FAR *lpstrPointerList;
    UINT wPointers = 0;
    UINT wHeaderSize = 0;
    LPSTR lpDefaultCommandItem = NULL;
    UINT wDefaultArgumentPosition;

    if (lpwOpenError != NULL)
        *lpwOpenError = 0;

// If the parameter pointer is NULL, return
    if (lpstrParams == NULL)
    {
        DOUT ("Warning:  lpstrParams is null in mciParseParams()\r\n");
        return 0;
    }

    if ((lpstrPointerList =
         mciAlloc ((MCI_MAX_PARAM_SLOTS + 1) * sizeof (LPSTR)))
        == NULL)
    {
        *lpPointerList = NULL;
        return MCIERR_OUT_OF_MEMORY;
    }

// If this is the "open" command then allow parameter errors
    bOpenCommand = lstrcmpi (lpCommandList, szOpen) == 0;

/* Clear all the flags */
    *lpdwFlags = 0;

/* Initialize the entry for the callback message window handle */
    wHeaderSize += sizeof (DWORD);
    if (wHeaderSize > wParamsSize)
    {
        wErr = MCIERR_PARAM_OVERFLOW;
        goto error_exit;
    }

/* Skip past the header */
    lpFirstCommandItem = (LPSTR)lpCommandList +
        mciEatCommandEntry (lpCommandList, NULL, NULL);

    wLen = mciEatCommandEntry (lpFirstCommandItem, &dwValue, &wID);
/* Make room in lpdwOutputParams for the return arguments if any */
    if (wID == MCI_RETURN)
    {
        lpFirstCommandItem += wLen;
        wHeaderSize += mciGetParamSize (dwValue, wID);
        if (wHeaderSize > wParamsSize)
        {
            wErr = MCIERR_PARAM_OVERFLOW;
            goto error_exit;
        }
    }

    lpOutputParams += wHeaderSize;

// Scan the parameter string looking up each parameter in the given command
// list

    while (TRUE)
    {
        LPCSTR lpstrArgument = NULL;

/* Remove leading blanks */
        while (*lpstrParams == ' ') ++lpstrParams;

/* Break at end of parameter string */
        if (*lpstrParams == '\0') break;

/* Scan for this parameter in the command list */
        lpCurrentCommandItem = lpFirstCommandItem;

        wLen = mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);

        wArgumentPosition = 0;

/* While there are more tokens in the Command List */
        while (wID != MCI_END_COMMAND)
        {
/* Check for a default argument if not already read */
            if (lpDefaultCommandItem == NULL &&
                *lpCurrentCommandItem == '\0')
            {
// Remember default argument
                lpDefaultCommandItem = lpCurrentCommandItem;
                dwDefaultValue = dwValue;
                wDefaultID = wID;
                wDefaultArgumentPosition = wArgumentPosition;
//              break;
            }
/* Check to see if this token matches */
            else if ((lpstrArgument =
                mciCheckToken (lpCurrentCommandItem, lpstrParams)) != NULL)
                break;

/* This token did not match the input but advance the argument position */
            wArgumentPosition += mciGetParamSize (dwValue, wID);

/* Go to next token */
            lpCurrentCommandItem += wLen;

// Is this command parameter a constant?
            if (wID == MCI_CONSTANT)
// Skip constant list
                do
                    lpCurrentCommandItem +=
                        mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);
                while (wID != MCI_END_CONSTANT);

            wLen = mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID);
        } /* while */

/* If there were no matches */
        if (lpstrArgument == NULL)
        {
// If a default argument exists then try it
            if (lpDefaultCommandItem != NULL)
            {
                lpstrArgument = lpstrParams;
                dwValue = dwDefaultValue;
                wID = wDefaultID;
                lpCurrentCommandItem = lpDefaultCommandItem;
                wArgumentPosition = wDefaultArgumentPosition;
            } else
            {
// Allow missing paramters on OPEN command if indicated by a non-null
// lpwOpenError address
                if (!bOpenCommand || lpwOpenError == NULL)
                {
                    wErr = MCIERR_UNRECOGNIZED_KEYWORD;
                    goto error_exit;
                } else
                {
// Skip the parameter if OPEN command
                    while (*lpstrParams != ' ' && *lpstrParams != '\0')
                        ++lpstrParams;
                    if (lpwOpenError != NULL)
                        *lpwOpenError = MCIERR_UNRECOGNIZED_KEYWORD;
                    continue;
                }
            }
        }

/* Is there room in the output buffer for this argument? */
        if (wArgumentPosition + wHeaderSize + mciGetParamSize (dwValue, wID)
            > wParamsSize)
        {
            DOUT ("mciParseParams:  parameter space overflow\r\n");
            wErr = MCIERR_PARAM_OVERFLOW;
            goto error_exit;
        }

// Remove leading blanks
        while (*lpstrArgument == ' ') ++lpstrArgument;

/* Process this parameter, filling in any flags or arguments */
        if ((wErr = mciParseArgument (dwValue, wID,
                                      &lpstrArgument, lpdwFlags,
                                      &lpOutputParams[wArgumentPosition],
                                      lpCurrentCommandItem))
            != 0)
            goto error_exit;

        lpstrParams = lpstrArgument;

        if (wID == MCI_STRING)
        {
            if (wPointers >= MCI_MAX_PARAM_SLOTS)
            {
                DOUT ("Warning: Out of pointer list slots in mciParseParams\r\n");
                break;
            }

            (DWORD)lpstrPointerList[wPointers++] =
                *(LPDWORD)&lpOutputParams[wArgumentPosition];
        }

/* Continue reading the parameter string */
    } /* while */

// Terminate list
    lpstrPointerList[wPointers] = NULL;
// Copy reference for caller
    *lpPointerList = lpstrPointerList;
// Return Success
    return 0;

error_exit:
    *lpPointerList = NULL;
// Terminate list
    lpstrPointerList[wPointers] = NULL;
    mciParserFree (lpstrPointerList);
    return wErr;
}

/*
 * @doc INTERNAL  MCI
 * @func UINT | mciParseCommand | This function converts an MCI
 * control string to an MCI control message suitable for sending to
 * <f mciSendCommand>.  The input string usually comes from <f mciSendString>
 * and always has the device name stripped off the front.
 *
 * @parm UINT | wDeviceID | Identifies the device. First searches the parsing
 * table belonging to the driver.
 * Then searches the command tables matching the type
 * of the given device.  Then searches the core command table.
 *
 * @parm LPSTR | lpstrCommand | An MCI control command without
 * a device name prefix.  There must be no leading or trailing
 * blanks.
 *
 * @parm LPCSTR | lpstrDeviceName | The device name (second token on the
 * command line).  It is used to identify the device type.
 *
 * @parm LPSTR FAR * | lpCommandList | If not NULL then the address of
 * the command list for the parsed command (if successful) is copied here.
 * It is used later by mciSendString when parsing arguments
 *
 * @parm UINT FAR* | lpwTable | The table resource ID to be unlocked
 * after parsing.  Returned to caller.
 *
 * @rdesc Returns the command ID or 0 if not found.
 *
 */
UINT PASCAL NEAR
mciParseCommand(
    UINT wDeviceID,
    LPSTR lpstrCommand,
    LPCSTR lpstrDeviceName,
    LPSTR FAR * lpCommandList,
    UINT FAR* lpwTable)
{
    LPSTR lpCommandItem;
    UINT wMessage;

// Put the command in lower case
//!!    mciToLower (lpstrCommand);

// Look up lpstrCommand in the parser's command tables.
    if ((lpCommandItem = FindCommandItem (wDeviceID, lpstrDeviceName,
                                          lpstrCommand,
                                          &wMessage, lpwTable))
        == NULL)
        return 0;

/* Return the command list to the caller */
    if (lpCommandList != NULL)
        *lpCommandList = lpCommandItem;
    else
       DOUT ("Warning: NULL lpCommandList in mciParseCommanad\r\n");

    return wMessage;
}

/*
 * @doc INTERNAL MCI
 * @func void | mciParserFree | Free any buffers allocated to
 * receive string arguments.
 *
 * @parm LPSTR FAR * | lpstrPointerList | A NULL terminated list of far
 * pointers to strings to be free'd
 *
 */
void PASCAL NEAR
mciParserFree(
    LPSTR FAR *lpstrPointerList
    )
{
    LPSTR FAR *lpstrOriginal = lpstrPointerList;

    if (lpstrPointerList == NULL)
        return;

    while (*lpstrPointerList != NULL)
        mciFree (*lpstrPointerList++);

    mciFree (lpstrOriginal);
}
