/*******************************Module*Header*********************************\ * Module Name: mciparse.c * * Media Control Architecture Command Parser * * Created: 3/2/90 * Author: DLL (DavidLe) * * History: * 5/22/91: Ported to Win32 - NigelT * 4 Mar 1992: SteveDav - much work for NT. Bring up to Win 3.1 level * * Copyright (c) 1991-1998 Microsoft Corporation * \******************************************************************************/ //**************************************************************************** // // This has to be defined in order to pick up the // correct version of MAKEINTRESOURCE // //**************************************************************************** #define UNICODE /***************************************************************************** * Notes: * * * * MCI command tables are (normally) loaded from resource type files. The * * format of a command table is shown below. Note that because the table * * contains string data, the binary values are UNALIGNED. This causes * * specific problems on MIPS machines. * * * * Because of compatibility with Windows 3.1 the binary data is WORD size * * * * Table format: * * * * verb\0 MCI_MESSAGE,0 MCI_command_type * * * * e.g. * * 'o' 'p' 'e' 'n' 03 08 00 00 00 'n' 'o' 't' 'i' 'f' 'y' 00 * * 01 00 00 00 00 05 00 * * * * which is "open" MCI_OPEN,0, MCI_COMMAND_HEAD * * "notify" MCI_NOTIFY,0 MCI_FLAG * * * * beware of the byte ordering! * * * ****************************************************************************/ #include "winmmi.h" #include "mci.h" #include "wchar.h" #include #define _INC_WOW_CONVERSIONS #include "mmwow32.h" extern WSZCODE wszOpen[]; // in MCI.C STATICFN UINT mciRegisterCommandTable( HANDLE hResource, PUINT lpwIndex, UINT wType, HANDLE hModule); STATICFN UINT mciParseArgument ( UINT uMessage, DWORD dwValue, UINT wID, LPWSTR FAR *lplpstrOutput, LPDWORD lpdwFlags, LPWSTR lpArgument, LPWSTR lpCurrentCommandItem); // // Define the init code for this file. This is commented out in debug builds // so that codeview doesn't get confused. #if DBG extern int mciDebugLevel; #endif // Number of command tables registered, including "holes" STATICDT UINT number_of_command_tables = 0; // Command table list COMMAND_TABLE_TYPE command_tables[MAX_COMMAND_TABLES]; STATICDT WSZCODE wszTypeTableExtension[] = L".mci"; STATICDT WSZCODE wszCoreTable[] = L"core"; // Core table is loaded when the first MCI command table is requested STATICDT BOOL bCoreTableLoaded = FALSE; // One element for each device type. Value is the table type to use // or 0 if there is no device type specific table. STATICDT UINT 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 }; /* * @doc INTERNAL MCI * @func UINT | mciEatCommandEntry | Read a command resource entry and * return its length and its value and identifier * * @parm LPWCSTR | lpEntry | The start of the command resource entry * * @parm LPDWORD | lpValue | The value of the entry, returned to caller * May be NULL * * @parm PUINT | lpID | The identifier of the entry, returned to caller * May be NULL * * @rdesc The total number of bytes in the entry * */ UINT mciEatCommandEntry ( LPCWSTR lpEntry, LPDWORD lpValue, PUINT lpID) { LPCWSTR lpScan = lpEntry; LPBYTE lpByte; #if DBG DWORD Value; UINT Id; #endif // NOTE: The data will generally be UNALIGNED /* Skip to end */ while (*lpScan++ != '\0'){} /* lpScan now points at the byte beyond the terminating zero */ lpByte = (LPBYTE)lpScan; if (lpValue != NULL) { *lpValue = *(UNALIGNED DWORD *)lpScan; } #if DBG Value = *(UNALIGNED DWORD *)lpScan; #endif lpByte += sizeof(DWORD); if (lpID != NULL) { *lpID = *(UNALIGNED WORD *)lpByte; } #if DBG Id = *(UNALIGNED WORD *)lpByte; #endif lpByte += sizeof(WORD); // // WARNING !! This assumes that the table being looked at has WORD // size entries in the RCDATA resource // #if DBG dprintf5(("mciEatCommandEntry(%ls) Value: %x Id: %x", lpEntry, Value, Id)); #endif return (UINT)(lpByte - (LPBYTE)lpEntry); // Total size of entry in bytes } // // Return the size used by this token in the parameter list // UINT mciGetParamSize ( DWORD dwValue, UINT wID) { // MCI_RETURN returns 8 for sizeof(STRING) as there is a length // field as well as the string pointer. For non MCI_RETURN uses // of MCI_STRING we should return 4 (== sizeof pointer) // Similarly, MCI_CONSTANT used within MCI_RETURN is size 0, but // size 4 when used as an input parameter. if (wID == MCI_RETURN) { if (dwValue==MCI_STRING) { return(8); } else if (dwValue==MCI_CONSTANT) { wID = 0; } else { wID=dwValue; } } switch (wID) { case MCI_CONSTANT: case MCI_INTEGER: case MCI_STRING: case MCI_HWND: case MCI_HPAL: case MCI_HDC: return sizeof(DWORD_PTR); // In Win64, sizeof pointer is 8 case MCI_RECT: return sizeof(RECT); } // Note that some items will not be found - deliberately. For example // MCI_FLAG causes 0 to be returned. return 0; } /* * @doc INTERNAL MCI * @func UINT | mciRegisterCommandTable | This function adds a new * table for the MCI parser. * * @parm HANDLE | hResource | Handle to the RCDATA resource * * @parm PUINT | 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. * * @rdesc Returns the command table index number that was assigned or MCI_ERROR_VALUE * on error. * */ STATICFN UINT mciRegisterCommandTable ( HANDLE hResource, PUINT lpwIndex, UINT wType, HANDLE hModule) { UINT uID; /* First check for free slots */ mciEnter("mciRegisterCommandTable"); for (uID = 0; uID < number_of_command_tables; ++uID) { if (command_tables[uID].hResource == NULL) { break; } } /* If no empty slots then allocate another one */ if (uID >= number_of_command_tables) { if (number_of_command_tables == MAX_COMMAND_TABLES) { dprintf1(("mciRegisterCommandTable: No more tables")); mciFree(lpwIndex); // Cannot use it - must free it mciLeave("mciRegisterCommandTable"); return (UINT)MCI_ERROR_VALUE; } else { uID = number_of_command_tables++; // The table goes at the end of the list } } /* Fill in the slot */ command_tables[uID].wType = wType; command_tables[uID].hResource = hResource; command_tables[uID].lpwIndex = lpwIndex; command_tables[uID].hModule = hModule; #if DBG command_tables[uID].wLockCount = 0; #endif // now that hResource has been filled in marking the entry as used // we can allow others access. mciLeave("mciRegisterCommandTable"); #if DBG if (mciDebugLevel > 2) { dprintf2(("mciRegisterCommandTable INFO: assigned slot %d", uID)); dprintf2(("mciRegisterCommandTable INFO: #tables is %d", number_of_command_tables)); } #endif return uID; } /* * @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 HANDLE | hInstance | The instance of the module whose executable * file contains the resource. This parameter is ignored if an external file * is found. * * @parm LPCWSTR | 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 MCI_ERROR_VALUE * on error. * */ UINT mciLoadCommandResource ( HANDLE hInstance, LPCWSTR lpResName, UINT wType) { BOOL fResType = !HIWORD(lpResName); PUINT lpwIndex, lpwScan; HANDLE hExternal = NULL; HANDLE hResource; HANDLE hResInfo; LPWSTR lpResource, lpScan; int nCommands = 0; UINT wLen; UINT wID; // Name + '.' + Extension + '\0' WCHAR strFile[8 + 1 + 3 + 1]; LPWSTR lpstrFile = strFile; LPCWSTR lpstrType = lpResName; #if DBG if (!fResType) { dprintf3(("mciLoadCommandResource INFO: Resource name >%ls< ", (LPWSTR)lpResName)); } else if (LOWORD(lpResName)) { dprintf3(("mciLoadCommandResource INFO: Resource ID >%d<", (UINT)LOWORD(lpResName))); } else { dprintf3(("mciLoadCommandResource INFO: NULL resource pointer")); } #endif // Initialize the device list if (!MCI_bDeviceListInitialized && !mciInitDeviceList()) { return (UINT)MCI_ERROR_VALUE; // MCIERR_OUT_OF_MEMORY; } // Load the core table if its not already there if (!bCoreTableLoaded) { bCoreTableLoaded = TRUE; // Now we can call ourselves recursively to first load the core // table. Check if this is a request to load CORE - if yes, // simply drop through. // If its not our core table being loaded... // which is decided by comparing the string with CORE, or if a // resource id has been given, the resource id is ID_CORE and comes // from our module // The test is structured this way so that lstrcmpiW is only called // if we have a valid pointer. #define fNotCoreTable ( fResType \ ? ((hInstance != ghInst) || (ID_CORE_TABLE != (UINT)(UINT_PTR)lpResName)) \ : (0 != lstrcmpiW (wszCoreTable, (LPWSTR)lpResName))) if (fNotCoreTable) { // We are not being asked to load the core table. So we // explicitly load the core table first if (mciLoadCommandResource (ghInst, MAKEINTRESOURCE(ID_CORE_TABLE), 0) == MCI_ERROR_VALUE) { dprintf1(("mciLoadCommandResource: Cannot load core table")); } } } // Unless this is a resource ID, go and look for a file if (!fResType) { WCHAR ExpandedName[MAX_PATH]; LPWSTR FilePart; // Check for a file with the extension ".mci" // Copy up to the first eight characters of device type // !!LATER!! Try a check for a resource first, then a file while (lpstrType < lpResName + 8 && *lpstrType != '\0') { *lpstrFile++ = *lpstrType++; } // Tack extension onto end wcscpy (lpstrFile, wszTypeTableExtension); // If the file exists and can be loaded then set flag to use it. // (Otherwise we will try and load the resource from WINMM.DLL.) if (!SearchPathW(NULL, strFile, NULL, MAX_PATH, ExpandedName, &FilePart)) { hExternal = NULL; } else { UINT OldErrorMode; OldErrorMode = SetErrorMode(SEM_FAILCRITICALERRORS); // Use "ExpandedName" to prevent a second search taking place hExternal = LoadLibraryExW( ExpandedName, //strFile, NULL, DONT_RESOLVE_DLL_REFERENCES); SetErrorMode(OldErrorMode); } } // Load the given table from the file or from the module if not found if (hExternal != NULL && (hResInfo = FindResourceW(hExternal, lpResName, RT_RCDATA )) != NULL) { hInstance = hExternal; } else { hResInfo = FindResourceW(hInstance, lpResName, RT_RCDATA ); } if (hResInfo == NULL) { #if DBG if (!fResType) { dprintf3(("mciLoadCommandResource Cannot find command resource name >%ls< ", (LPWSTR)lpResName)); } else { dprintf3(("mciLoadCommandResource Cannot find command resource ID >%d<", (UINT)LOWORD(lpResName))); } #endif if (NULL != hExternal) { FreeLibrary(hExternal); // Clean up after ourselves } return (UINT)MCI_ERROR_VALUE; } if ((hResource = LoadResource (hInstance, hResInfo)) == NULL) { #if DBG if (!fResType) { dprintf3(("mciLoadCommandResource Cannot load command resource name >%ls< ", (LPWSTR)lpResName)); } else { dprintf3(("mciLoadCommandResource Cannot load command resource ID >%d<", (UINT)LOWORD(lpResName))); } #endif if (NULL != hExternal) { FreeLibrary(hExternal); // Clean up after ourselves } return (UINT)MCI_ERROR_VALUE; } if ((lpResource = LockResource (hResource)) == NULL) { dprintf1(("mciLoadCommandResource: Cannot lock resource")); FreeResource (hResource); if (NULL != hExternal) { FreeLibrary(hExternal); // Clean up after ourselves } return (UINT)MCI_ERROR_VALUE; } /* Count the number of commands */ lpScan = lpResource; while (TRUE) { (LPBYTE)lpScan = (LPBYTE)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 one command in the table if (nCommands == 0) { dprintf1(("mciLoadCommandResource: No commands in the specified table")); UnlockResource (hResource); FreeResource (hResource); if (NULL != hExternal) { FreeLibrary(hExternal); // Clean up after ourselves } return (UINT)MCI_ERROR_VALUE; } else { dprintf3(("mciLoadCommandResource: %d commands in the specified table", nCommands)); } // Allocate storage for the command table index // Leave room for a MCI_TABLE_NOT_PRESENT entry to terminate it if ((lpwIndex = mciAlloc (sizeof (*lpwIndex) * (nCommands + 1))) == NULL) { dprintf1(("mciLoadCommandResource: cannot allocate command table index")); UnlockResource (hResource); FreeResource (hResource); if (NULL != hExternal) { FreeLibrary(hExternal); // Clean up after ourselves } return (UINT)MCI_ERROR_VALUE; } /* 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 offset index to this command from start of resource *lpwScan++ = (UINT)((LPBYTE)lpScan - (LPBYTE)lpResource); } else if (wID == MCI_END_COMMAND_LIST) { // Mark the end of the table *lpwScan = (UINT)MCI_TABLE_NOT_PRESENT; break; } (LPBYTE)lpScan = (LPBYTE)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 MCI_ERROR_VALUE * on error. */ UINT mciLoadTableType ( UINT wType) { UINT wID; #ifdef OLD WCHAR buf[MCI_MAX_DEVICE_TYPE_LENGTH]; #endif // 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 (UINT)MCI_ERROR_VALUE; } // Load string that corresponds to table type #ifdef OLD #ifdef WIN31CODE // Load string that corresponds to table type buf[0] = 0; // In case load string fails to set anything LoadString (ghInst, table_types[wType - MCI_DEVTYPE_FIRST], buf, sizeof(buf)); { //Must be at least one character in type name int nTypeLen; if ((nTypeLen = wcslen (buf)) < 1) return MCI_ERROR_VALUE; } #else // Load string that corresponds to table type buf[0] = 0; // In case load string fails to set anything if (!LoadString (ghInst, table_types[wType - MCI_DEVTYPE_FIRST], buf, sizeof(buf))) { //Must put at least one character into type name return MCI_ERROR_VALUE; } #endif // WIN31CODE // Register the table with MCI return mciLoadCommandResource (ghInst, buf, wType); #else // not old // Command tables are stored as RCDATA blocks with an id of the device type // If mciLoadCommandResource fails to find the command table then it // will return MCI_ERROR_VALUE //if (!FindResource(ghInst, wType, RT_RCDATA)) // return MCI_ERROR_VALUE; // // Register the table with MCI return mciLoadCommandResource (ghInst, MAKEINTRESOURCE(wType), wType); #endif } /* * @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 APIENTRY mciFreeCommandResource ( UINT wTable) { MCIDEVICEID wID; HANDLE hResource; PUINT lpwIndex; dprintf3(("mciFreeCommandResource INFO: Free table %d", wTable)); dprintf3(("mciFreeCommandResource INFO: Lockcount is %d", command_tables[wTable].wLockCount)); /* Validate input -- do not let the core table be free'd */ if (wTable == MCI_TABLE_NOT_PRESENT || wTable >= number_of_command_tables) { #if DBG // wTable == MCI_TABLE_NOT_PRESENT is OK if (wTable != MCI_TABLE_NOT_PRESENT) { dprintf1(("mciFreeCommandResource: Cannot free table number %d", wTable)); } #endif return FALSE; } mciEnter("mciFreeCommandResource"); // 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) { #if DBG if (mciDebugLevel > 2) { dprintf1(("mciFreeCommandResource INFO: table in use")); } #endif mciLeave("mciFreeCommandResource"); 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) { #if DBG if (mciDebugLevel > 2) DOUT(("mciFreeCommandResource INFO: resource in use\r\n")); #endif mciLeave("mciFreeCommandResource"); return FALSE; } #endif hResource = command_tables[wTable].hResource; command_tables[wTable].hResource = NULL; // This slot can now be picked up by someone else lpwIndex = command_tables[wTable].lpwIndex; command_tables[wTable].lpwIndex = NULL; command_tables[wTable].wType = 0; FreeResource (hResource); mciFree (lpwIndex); hResource = command_tables[wTable].hModule; mciLeave("mciFreeCommandResource"); if (hResource != NULL) { FreeLibrary (hResource); } // Make space at top of list if (wTable == number_of_command_tables - 1) { --number_of_command_tables; } dprintf3(("mciFreeCommandResource INFO: number_of_command_tables: %d", number_of_command_tables)); return TRUE; } #if DBG void mciCheckLocks () { UINT wTable; if (mciDebugLevel <= 2) { return; } for (wTable = 0; wTable < number_of_command_tables; ++wTable) { if (command_tables[wTable].hResource == NULL) { continue; } dprintf2(("mciCheckLocks INFO: table %d Lock count %d", wTable, command_tables[wTable].wLockCount)); // dprintf2(("user: %x ", GlobalFlags (command_tables[wTable].hResource) & GMEM_LOCKCOUNT)); // // if (GlobalFlags (command_tables[wTable].hResource) & GMEM_DISCARDABLE) { // dprintf(("discardable")); // } else { // dprintf(("NOT discardable")); // } } } #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 mciUnlockCommandTable ( UINT wCommandTable) { UnlockResource(command_tables[wCommandTable].hResource); #if DBG --command_tables[wCommandTable].wLockCount; if (mciDebugLevel > 2) { dprintf2(("mciUnlockCommandTable INFO: table %d", wCommandTable)); mciCheckLocks(); } #endif return TRUE; } /* * @doc INTERNAL MCI * @func LPWSTR | FindCommandInTable | Look up the given * command string in the GIVEN parser command table * * @parm UINT | wTable | Command table to use * * @parm LPCWSTR | 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 PUINT | 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 ffr the input command string. * * @comm If the command is found, the command resource will be locked on exit. * */ LPWSTR FindCommandInTable ( UINT wTable, LPCWSTR lpstrCommand, PUINT lpwMessage) { PUINT lpwIndex; LPWSTR lpResource, lpstrThisCommand; UINT wMessage; #if DBG if (HIWORD(lpstrCommand)) { dprintf3(("FindCommandInTable(%04XH, %ls)", wTable, lpstrCommand)); } else { dprintf3(("FindCommandInTable(%04XH, id = %x)", wTable, (UINT)LOWORD(lpstrCommand))); } #endif // /* Validate table */ // mciEnter("FindCommandInTable"); 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, wszCoreTable, 0) == MCI_ERROR_VALUE) if (mciLoadCommandResource (ghInst, (LPCWSTR)ID_CORE_TABLE, 0) == MCI_ERROR_VALUE) { mciLeave("FindCommandInTable"); dprintf1(("FindCommandInTable: cannot load core table")); return NULL; } } else { mciLeave("FindCommandInTable"); dprintf1(("FindCommandInTable: invalid table ID: %04XH", wTable)); return NULL; } } if ((lpResource = LockResource (command_tables[wTable].hResource)) == NULL) { mciLeave("FindCommandInTable"); dprintf1(("MCI FindCommandInTable: Cannot lock table resource")); return NULL; } #if DBG ++command_tables[wTable].wLockCount; #endif // // Look at each command in the table // We use the index table rather than the return value from // mciEatCommandEntry to step through the table // lpwIndex = command_tables[wTable].lpwIndex; if (lpwIndex == NULL) { mciLeave("FindCommandInTable"); dprintf1(("MCI FindCommandInTable: null command table index")); return NULL; } while (*lpwIndex != MCI_TABLE_NOT_PRESENT) { lpstrThisCommand = (LPWSTR)(*lpwIndex++ + (LPBYTE)lpResource); // // Get message number from the table // mciEatCommandEntry ((LPCWSTR)lpstrThisCommand, (LPDWORD)&wMessage, NULL); // // Does this command match the input? // IF we have a string pointer, check the command name matches, // OR for a message, check the message values match // if (HIWORD (lpstrCommand) != 0 && lstrcmpiW(lpstrThisCommand, lpstrCommand) == 0 || HIWORD (lpstrCommand) == 0 && wMessage == (UINT)LOWORD(PtrToUlong(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 // mciLeave("FindCommandInTable"); dprintf3(("mciFindCommandInTable: found >%ls< Message %x", lpstrThisCommand, wMessage)); return lpstrThisCommand; } // // Strings don't match, go to the next command in the table // } UnlockResource (command_tables[wTable].hResource); #if DBG --command_tables[wTable].wLockCount; #endif mciLeave("FindCommandInTable"); dprintf3((" ...not found")); return NULL; } /* * @doc INTERNAL MCI * @func LPWSTR | FindCommandItem | Look up the given * command string in the parser command tables * * @parm MCIDEVICEID | wDeviceID | The device ID used for this command. * If 0 then only the system core command table is searched. * * @parm LPCWSTR | lpstrType | The type name of the device * * @parm LPCWSTR | 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 PUINT | lpwMessage | The message corresponding to the command * Returned to caller. * * @parm LPUINT | 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. */ LPWSTR FindCommandItem ( MCIDEVICEID wDeviceID, LPCWSTR lpstrType, LPCWSTR lpstrCommand, PUINT lpwMessage, PUINT lpwTable) { LPWSTR lpCommand = NULL; UINT wTable; LPMCI_DEVICE_NODE nodeWorking; UINT uDeviceType = 0; UNREFERENCED_PARAMETER(lpstrType); // // Only check hiword per comments above // if (HIWORD (lpstrCommand) != (WORD)NULL) { if (*lpstrCommand == '\0') { dprintf1(("MCI FindCommandItem: lpstrCommand is NULL or empty string")); return NULL; } else { dprintf3(("FindCommandItem(%ls)", lpstrCommand)); } } else { dprintf3(("FindCommandItem(command id = %x)", (UINT)LOWORD(lpstrCommand))); } // // 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 // mciEnter("FindCommandItem"); if (!MCI_VALID_DEVICE_ID (wDeviceID) || (NULL == (nodeWorking = MCI_lpDeviceList[wDeviceID]))) { dprintf1(("MCI FindCommandItem: Invalid device ID or pointer")); mciLeave("FindCommandItem"); return NULL; } uDeviceType = nodeWorking->wDeviceType; // // If there is a custom command table then use it // if ((wTable = nodeWorking->wCustomCommandTable) != MCI_TABLE_NOT_PRESENT) { lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage); if (lpCommand != NULL) { mciLeave("FindCommandItem"); 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) != MCI_TABLE_NOT_PRESENT) { lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage); if (lpCommand != NULL) { mciLeave("FindCommandItem"); goto exit; } } mciLeave("FindCommandItem"); } #if 0 // If no device was specified if (uDeviceType == 0 && lpstrType != NULL && *lpstrType != '\0') { // See if the type is one known uDeviceType = mciLookUpType (lpstrType); if (uDeviceType == 0) { // Otherwise see if the type is an element with a known extension WCHAR strTemp[MCI_MAX_DEVICE_NAME_LENGTH]; if (mciExtractDeviceType (lpstrType, strTemp, sizeof(strTemp))) uDeviceType = mciLookUpType (strTemp); } } /* If the command was not found in the custom table look in the type specific table */ if (uDeviceType != 0) { wTable = mciLoadTableType (uDeviceType); if (wTable != MCI_TABLE_NOT_PRESENT) { lpCommand = FindCommandInTable (wTable, lpstrCommand, lpwMessage); if (lpCommand != NULL) { goto exit; } } } #endif // // 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 = (UINT)MCI_TABLE_NOT_PRESENT; } exit:; if (lpwTable != NULL) { *lpwTable = wTable; } #if DBG if (mciDebugLevel > 2) { dprintf2(("FindCommandItem INFO: check locks...")); mciCheckLocks(); } #endif #if DBG dprintf3((" found: %ls in table %d", lpCommand ? lpCommand : L"(NULL)", wTable)); #endif return lpCommand; } /* * @doc INTERNAL MCI * @func LPWSTR | 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 LPCWSTR | lpstrToken | The command token to check * * @parm LPCWSTR | lpstrParam | The input parameter * * @rdesc NULL if no match, otherwise points to the first character * after the parameter * */ STATICFN LPWSTR mciCheckToken ( LPCWSTR lpstrToken, LPCWSTR 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 the next character is a blank, skip to the next // non-blank. if (*lpstrToken == ' ') { while (*lpstrParam == ' ') { ++lpstrParam; } } else { lpstrParam++; } lpstrToken++; } if (*lpstrToken != '\0'|| (*lpstrParam != '\0' && *lpstrParam != ' ')) { return NULL; } else { return (LPWSTR)lpstrParam; } } /* * @doc INTERNAL MCI * @func BOOL | mciParseInteger | Parse the given integer * * @parm LPWSTR 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 NEAR mciParseInteger ( LPCWSTR FAR * lplpstrInput, LPDWORD lpdwArgument) { LPCWSTR lpstrInput = *lplpstrInput; BOOL fDigitFound; DWORD dwResult; DWORD Shift = 0; 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) { dprintf1(("mciParseInteger: Bad integer: mixing signed and colonized forms")); return FALSE; } // Check for overflow in accumulated colonized byte if (dwResult > 0xFF) { dprintf1(("mciParseInteger: Overflow in accumulated colonized byte")); return FALSE; } // Copy and move to next byte converted in output *lpdwArgument += dwResult << Shift; Shift += 8; ++lpstrInput; // Initialize next colonized byte dwResult = 0; ++nDigitPosition; // Only allow four colonized components if (nDigitPosition > 3) { dprintf1(("mciParseInteger: Bad integer: Too many colonized components")); return FALSE; } } else { WCHAR cDigit = (WCHAR)(*lpstrInput++ - '0'); // Satisfies condition that at least one digit must be read fDigitFound = TRUE; if (dwResult > 0xFFFFFFFF / 10) { // Overflow if multiply was to occur dprintf1(("mciParseInteger: Multiply overflow pending")); return FALSE; } else { // Multiply for next digit dwResult *= 10; } #if 0 // WIN32 Danger Will Robinson horribly bogus technique used here! // Check to see if adding the new digit will overflow if (dwResult != 0 && (-(int)dwResult) <= (int)cDigit) { // Overflow will occur dprintf1(("mciParseInteger: Add overflow pending")); return FALSE; } #endif // Add new digit dwResult += cDigit; } } if (nDigitPosition == 0) { // No colonized components if (bSigned) { // Check for overflow from negation if (dwResult > 0x7FFFFFFF) { dprintf1(("mciParseInteger: Negation overflow")); return FALSE; } // Negate result because a '-' sign was parsed dwResult = (DWORD)-(int)dwResult; } *lpdwArgument = dwResult; } else // Store last colonized component { // Check for overflow if (dwResult > 0xFF) { dprintf1(("mciParseInteger: Yet another overflow")); return FALSE; } // Store component *lpdwArgument += dwResult << Shift; } *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. */ if (fDigitFound == FALSE || (*lpstrInput != ' ' && *lpstrInput != '\0')) { dprintf1(("mciParseInteger: syntax error")); return FALSE; } else { dprintf4(("mciParseInteger(%ls, %08XH)", *lplpstrInput, *lpdwArgument)); return TRUE; } } /* * @doc INTERNAL MCI * @func BOOL | mciParseConstant | Parse the given integer * * @parm LPWSTR 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 LPWSTR | lpItem | Pointer into command table. * * @rdesc Returns TRUE if not error * */ STATICFN BOOL mciParseConstant ( LPCWSTR FAR * lplpstrInput, LPDWORD lpdwArgument, LPWSTR lpItem) { LPWSTR lpPrev; DWORD dwValue; UINT wID; // Skip past constant header (LPBYTE)lpItem = (LPBYTE)lpItem + mciEatCommandEntry(lpItem, &dwValue, &wID); while (TRUE) { LPWSTR lpstrAfter; lpPrev = lpItem; (LPBYTE)lpItem = (LPBYTE)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 LPWSTR 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 mciParseArgument ( UINT uMessage, DWORD dwValue, UINT wID, LPWSTR FAR * lplpstrOutput, LPDWORD lpdwFlags, LPWSTR lpArgument, LPWSTR lpCurrentCommandItem) { LPCWSTR lpstrInput = *lplpstrOutput; UINT wRetval = 0; int dummy; /* Switch on the argument type */ dprintf2(("mciParseArgument: msg=%04x, value=%08x, argument=%ls", uMessage, dwValue, lpArgument)); switch (wID) { // The parameter is a flag case MCI_FLAG: break; case MCI_CONSTANT: if (*lpstrInput == '\0') { wRetval = MCIERR_NO_INTEGER; } else if (!mciParseConstant (&lpstrInput, (LPDWORD)lpArgument, lpCurrentCommandItem)) { wRetval = MCIERR_BAD_CONSTANT; } // This entire else clause is only for WOW which doesn't exist // on Win64 #ifndef _WIN64 else if ( WinmmRunningInWOW ) { // // Horrible hack: The command table does not contain // enough information to perform the thunk correctly, // hence this special case. // if ( uMessage == MCI_WINDOW && dwValue == MCI_OVLY_WINDOW_HWND && !IsWindow( (HWND)*(LPDWORD)lpArgument ) ) { *(HWND *)lpArgument = HWND32(LOWORD(*(LPDWORD)lpArgument)); } // If the message is MCI_SETVIDEO and we have // MCI_DGV_SETVIDEO_VALUE it is possible that we have to // convert the constant number to a palette handle, but ONLY // if the ITEM field is "palette handle". We may not know // that now as the string may be of the form: // setvideo alias to NNN palette handle // OR setvideo alias to NNN stream // Hence any hacking for WOW has to be done when the // parsing has been completed. } #endif // !WIN64 break; /* Deal with the integer specific cases */ case MCI_HDC: case MCI_HPAL: case MCI_INTEGER: case MCI_HWND: if (!mciParseInteger (&lpstrInput, (LPDWORD)lpArgument)) { wRetval = MCIERR_BAD_INTEGER; } #ifndef _WIN64 else if ( WinmmRunningInWOW ) { switch (wID) { case MCI_HPAL: /* The parameter has an HPAL argument, try to parse it */ // // If this specified hpal is not valid, mangle the hpal // so that it appears to originate from WOW. I use GetObject // to test the validity of the specified hpal. // #ifdef _WIN64 GetObject( (HPALETTE)*(PDWORD_PTR)lpArgument,sizeof(int), &dummy ); #else // !WIN64 if ( !GetObject( (HPALETTE)*(PDWORD_PTR)lpArgument, sizeof(int), &dummy ) ) { *(HPALETTE *)lpArgument = HPALETTE32(LOWORD(*(LPDWORD)lpArgument)); } #endif // !WIN64 break; case MCI_HWND: /* The parameter has an HWND argument, try to parse it */ // // If this specified hwnd is not valid, mangle the hwnd // so that it appears to originate from WOW. // if ( !IsWindow( (HWND)*(LPDWORD)lpArgument ) ) { *(HWND *)lpArgument = HWND32(LOWORD(*(LPDWORD)lpArgument)); } break; case MCI_HDC: // // If this specified hdc is not valid, mangle the hdc // so that it appears to originate from WOW. I use GetBkMode // to test the validity of the specified hdc. // if ( !GetBkMode( (HDC)*(LPDWORD)lpArgument ) ) { *(HDC *)lpArgument = HDC32(LOWORD(*(LPDWORD)lpArgument)); } break; case MCI_INTEGER: default: ; } } #endif // !WIN64 break; /* switch */ case MCI_RECT: { // Read in four integer parameters. Resulting structure is the // same as a Windows RECT LONG lTemp; int n; for (n = 0; n < 4; ++n) { if (!mciParseInteger (&lpstrInput, (LPDWORD)&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: { LPWSTR 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) { dprintf1(("mciParseArgument: error parsing string")); return wRetval; } *(PDWORD_PTR)lpArgument = (DWORD_PTR)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 = (LPWSTR)lpstrInput; return wRetval; } /* * @doc MCI INTERNAL * @func UINT | mciParseParams | Parse the command parameters * * @parm LPCWSTR | lpstrParams | The parameter string * * @parm LPCWSTR | 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 DWORD | dwParamsSize | The size allocated for the parameter list * * @parm LPWSTR 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 PUINT | 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 mciParseParams ( UINT uMessage, LPCWSTR lpstrParams, LPCWSTR lpCommandList, LPDWORD lpdwFlags, LPWSTR lpOutputParams, UINT wParamsSize, LPWSTR FAR * FAR *lpPointerList, PUINT lpwParsingError) { LPWSTR lpFirstCommandItem, lpCurrentCommandItem; UINT wArgumentPosition, wErr, wDefaultID; UINT uLen; UINT wID; DWORD dwValue, dwDefaultValue; BOOL bOpenCommand; LPWSTR FAR *lpstrPointerList; UINT wPointers = 0; UINT wHeaderSize; LPWSTR lpDefaultCommandItem = NULL; UINT wDefaultArgumentPosition; if (lpwParsingError != NULL) { *lpwParsingError = 0; } // If the parameter pointer is NULL, return if (lpstrParams == NULL) { dprintf1(("Warning: lpstrParams is null in mciParseParams()")); return 0; } if ((lpstrPointerList = mciAlloc ((MCI_MAX_PARAM_SLOTS + 1) * sizeof (LPWSTR))) == NULL) { *lpPointerList = NULL; return MCIERR_OUT_OF_MEMORY; } // If this is the "open" command then allow parameter errors bOpenCommand = lstrcmpiW((LPWSTR)lpCommandList, wszOpen) == 0; /* Clear all the flags */ *lpdwFlags = 0; /* Initialize the entry for the callback message window handle */ /* Each MCI parameter block uses the first word in the parameter */ /* block for the callback window handle. */ wHeaderSize = sizeof (((PMCI_GENERIC_PARMS)lpOutputParams)->dwCallback); if (wHeaderSize > wParamsSize) { // bit of our caller... wErr = MCIERR_PARAM_OVERFLOW; goto error_exit; } /* Skip past the header */ lpFirstCommandItem = (LPWSTR)((LPBYTE)lpCommandList + mciEatCommandEntry( lpCommandList, NULL, NULL )); uLen = mciEatCommandEntry (lpFirstCommandItem, &dwValue, &wID); /* Make room in lpdwOutputParams for the return arguments if any */ if (wID == MCI_RETURN) { (LPBYTE)lpFirstCommandItem = (LPBYTE)lpFirstCommandItem + uLen; wHeaderSize += mciGetParamSize (dwValue, wID); if (wHeaderSize > wParamsSize) { wErr = MCIERR_PARAM_OVERFLOW; goto error_exit; } } (LPBYTE)lpOutputParams = (LPBYTE)lpOutputParams + wHeaderSize; // Each output parameter is LPWSTR size // Scan the parameter string looking up each parameter in the given // command list while (TRUE) { LPCWSTR 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; wArgumentPosition = 0; uLen = mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID); /* 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 */ (LPBYTE)lpCurrentCommandItem = (LPBYTE)lpCurrentCommandItem + uLen; // Is this command parameter a constant? if (wID == MCI_CONSTANT) { // Skip constant list do (LPBYTE)lpCurrentCommandItem = (LPBYTE)lpCurrentCommandItem + mciEatCommandEntry (lpCurrentCommandItem, &dwValue, &wID); while (wID != MCI_END_CONSTANT); } uLen = 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 = (LPWSTR)lpstrParams; dwValue = dwDefaultValue; wID = wDefaultID; lpCurrentCommandItem = lpDefaultCommandItem; wArgumentPosition = wDefaultArgumentPosition; } else { // Allow missing paramters on OPEN command if indicated by a // non-null lpwParsingError address if (!bOpenCommand || lpwParsingError == NULL) { wErr = MCIERR_UNRECOGNIZED_KEYWORD; goto error_exit; } else { // Skip the parameter if OPEN command while (*lpstrParams != ' ' && *lpstrParams != '\0') ++lpstrParams; if (lpwParsingError != NULL) *lpwParsingError = MCIERR_UNRECOGNIZED_KEYWORD; continue; } } } /* Is there room in the output buffer for this argument? */ if (wArgumentPosition + wHeaderSize + mciGetParamSize (dwValue, wID) > wParamsSize) { dprintf1(("mciParseParams: parameter space overflow")); 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 (uMessage, dwValue, wID, (LPWSTR FAR *)&lpstrArgument, lpdwFlags, (LPWSTR)((LPBYTE)lpOutputParams + wArgumentPosition), lpCurrentCommandItem)) != 0) { goto error_exit; } lpstrParams = lpstrArgument; if (wID == MCI_STRING) { if (wPointers >= MCI_MAX_PARAM_SLOTS) { dprintf1(("Warning: Out of pointer list slots in mciParseParams")); break; } lpstrPointerList[wPointers++] = *((LPWSTR *)((LPBYTE)lpOutputParams + wArgumentPosition)); } /* Continue reading the parameter string */ } /* while */ // Terminate list lpstrPointerList[wPointers] = NULL; // Copy reference for caller *lpPointerList = lpstrPointerList; // // This is a hack to make sure that // the string version of MCI_SETVIDEO can actually set a palette // when called from a WOW app. // #ifndef _WIN64 if (WinmmRunningInWOW) { DWORD dummy; // To hold response from GetObject if ((uMessage == MCI_SETVIDEO) && (*lpdwFlags & MCI_DGV_SETVIDEO_VALUE) && (*lpdwFlags & MCI_DGV_SETVIDEO_ITEM) && (*(LPDWORD)lpOutputParams == MCI_DGV_SETVIDEO_PALHANDLE) && (!GetObject( (HPALETTE)*(((LPDWORD)lpOutputParams)+1), sizeof(int), &dummy ) )) { dprintf2(("Replacing WOW palette handle %x", *(HPALETTE *)(((LPDWORD)lpOutputParams)+1))); *(HPALETTE *)(((LPDWORD)lpOutputParams)+1) = HPALETTE32(LOWORD(*(((LPDWORD)lpOutputParams)+1) )); dprintf2(("WOW palette handle now %x", *(HPALETTE *)(((LPDWORD)lpOutputParams)+1))); } } #endif // !WIN64 // 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 * . The input string usually comes from * and always has the device name stripped off the front. * * @parm MCIDEVICEID | 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 LPWSTR | lpstrCommand | An MCI control command without * a device name prefix. There must be no leading or trailing * blanks. * * @parm LPCWSTR | lpstrDeviceName | The device name (second token on the * command line). It is used to identify the device type. * * @parm LPWSTR 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 PUINT | lpwTable | The table resource ID to be unlocked * after parsing. Returned to caller. * * @rdesc Returns the command ID or 0 if not found. * */ UINT mciParseCommand ( MCIDEVICEID wDeviceID, LPWSTR lpstrCommand, LPCWSTR lpstrDeviceName, LPWSTR * lpCommandList, PUINT lpwTable) { LPWSTR lpCommandItem; UINT wMessage; dprintf2(("mciParseCommand(%ls, %ls)", lpstrCommand ? lpstrCommand : L"(NULL)", lpstrDeviceName ? lpstrDeviceName : L"(NULL)")); // 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 { dprintf1(("Warning: NULL lpCommandList in mciParseCommand")); } return wMessage; } /* * @doc INTERNAL MCI * @func VOID | mciParserFree | Free any buffers allocated to * receive string arguments. * * @parm LPWSTR FAR * | lpstrPointerList | A NULL terminated list of far * pointers to strings to be free'd * */ VOID mciParserFree ( LPWSTR FAR *lpstrPointerList) { LPWSTR FAR *lpstrOriginal = lpstrPointerList; if (lpstrPointerList == NULL) { return; } while (*lpstrPointerList != NULL) { mciFree (*lpstrPointerList++); } mciFree (lpstrOriginal); }