Added WSASendDisconnect.
[wine] / dlls / winmm / mci.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2
3 /*
4  * MCI internal functions
5  *
6  * Copyright 1998/1999 Eric Pouech
7  *
8  * This library is free software; you can redistribute it and/or
9  * modify it under the terms of the GNU Lesser General Public
10  * License as published by the Free Software Foundation; either
11  * version 2.1 of the License, or (at your option) any later version.
12  *
13  * This library is distributed in the hope that it will be useful,
14  * but WITHOUT ANY WARRANTY; without even the implied warranty of
15  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16  * Lesser General Public License for more details.
17  *
18  * You should have received a copy of the GNU Lesser General Public
19  * License along with this library; if not, write to the Free Software
20  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21  */
22
23 #include "config.h"
24 #include "wine/port.h"
25
26 #include <stdlib.h>
27 #include <stdio.h>
28 #include <string.h>
29
30 #include "mmsystem.h"
31 #include "winbase.h"
32 #include "wingdi.h"
33 #include "winreg.h"
34 #include "winuser.h"
35
36 #include "wine/mmsystem16.h"
37 #include "wine/winbase16.h"
38 #include "digitalv.h"
39 #include "winemm.h"
40
41 #include "wine/debug.h"
42
43 WINE_DEFAULT_DEBUG_CHANNEL(mci);
44
45 static  int                     MCI_InstalledCount;
46 static  LPSTR                   MCI_lpInstallNames = NULL;
47
48 /* First MCI valid device ID (0 means error) */
49 #define MCI_MAGIC 0x0001
50
51 /* dup a string and uppercase it */
52 inline static LPSTR str_dup_upper( LPCSTR str )
53 {
54     INT len = strlen(str) + 1;
55     LPSTR p = HeapAlloc( GetProcessHeap(), 0, len );
56     if (p)
57     {
58         memcpy( p, str, len );
59         CharUpperA( p );
60     }
61     return p;
62 }
63
64 /**************************************************************************
65  *                              MCI_GetDriver                   [internal]
66  */
67 LPWINE_MCIDRIVER        MCI_GetDriver(UINT16 wDevID)
68 {
69     LPWINE_MCIDRIVER    wmd = 0;
70
71     EnterCriticalSection(&WINMM_IData->cs);
72     for (wmd = WINMM_IData->lpMciDrvs; wmd; wmd = wmd->lpNext) {
73         if (wmd->wDeviceID == wDevID)
74             break;
75     }
76     LeaveCriticalSection(&WINMM_IData->cs);
77     return wmd;
78 }
79
80 /**************************************************************************
81  *                              MCI_GetDriverFromString         [internal]
82  */
83 UINT    MCI_GetDriverFromString(LPCSTR lpstrName)
84 {
85     LPWINE_MCIDRIVER    wmd;
86     UINT                ret = 0;
87
88     if (!lpstrName)
89         return 0;
90
91     if (!lstrcmpiA(lpstrName, "ALL"))
92         return MCI_ALL_DEVICE_ID;
93
94     EnterCriticalSection(&WINMM_IData->cs);
95     for (wmd = WINMM_IData->lpMciDrvs; wmd; wmd = wmd->lpNext) {
96         if (wmd->lpstrElementName && strcmp(wmd->lpstrElementName, lpstrName) == 0) {
97             ret = wmd->wDeviceID;
98             break;
99         }
100         if (wmd->lpstrDeviceType && strcasecmp(wmd->lpstrDeviceType, lpstrName) == 0) {
101             ret = wmd->wDeviceID;
102             break;
103         }
104         if (wmd->lpstrAlias && strcasecmp(wmd->lpstrAlias, lpstrName) == 0) {
105             ret = wmd->wDeviceID;
106             break;
107         }
108     }
109     LeaveCriticalSection(&WINMM_IData->cs);
110
111     return ret;
112 }
113
114 /**************************************************************************
115  *                      MCI_MessageToString                     [internal]
116  */
117 const char* MCI_MessageToString(UINT16 wMsg)
118 {
119     static char buffer[100];
120
121 #define CASE(s) case (s): return #s
122
123     switch (wMsg) {
124         CASE(MCI_BREAK);
125         CASE(MCI_CLOSE);
126         CASE(MCI_CLOSE_DRIVER);
127         CASE(MCI_COPY);
128         CASE(MCI_CUE);
129         CASE(MCI_CUT);
130         CASE(MCI_DELETE);
131         CASE(MCI_ESCAPE);
132         CASE(MCI_FREEZE);
133         CASE(MCI_PAUSE);
134         CASE(MCI_PLAY);
135         CASE(MCI_GETDEVCAPS);
136         CASE(MCI_INFO);
137         CASE(MCI_LOAD);
138         CASE(MCI_OPEN);
139         CASE(MCI_OPEN_DRIVER);
140         CASE(MCI_PASTE);
141         CASE(MCI_PUT);
142         CASE(MCI_REALIZE);
143         CASE(MCI_RECORD);
144         CASE(MCI_RESUME);
145         CASE(MCI_SAVE);
146         CASE(MCI_SEEK);
147         CASE(MCI_SET);
148         CASE(MCI_SPIN);
149         CASE(MCI_STATUS);
150         CASE(MCI_STEP);
151         CASE(MCI_STOP);
152         CASE(MCI_SYSINFO);
153         CASE(MCI_UNFREEZE);
154         CASE(MCI_UPDATE);
155         CASE(MCI_WHERE);
156         CASE(MCI_WINDOW);
157         /* constants for digital video */
158         CASE(MCI_CAPTURE);
159         CASE(MCI_MONITOR);
160         CASE(MCI_RESERVE);
161         CASE(MCI_SETAUDIO);
162         CASE(MCI_SIGNAL);
163         CASE(MCI_SETVIDEO);
164         CASE(MCI_QUALITY);
165         CASE(MCI_LIST);
166         CASE(MCI_UNDO);
167         CASE(MCI_CONFIGURE);
168         CASE(MCI_RESTORE);
169 #undef CASE
170     default:
171         sprintf(buffer, "MCI_<<%04X>>", wMsg);
172         return buffer;
173     }
174 }
175
176 /**************************************************************************
177  *                              MCI_GetDevTypeFromFileName      [internal]
178  */
179 static  DWORD   MCI_GetDevTypeFromFileName(LPCSTR fileName, LPSTR buf, UINT len)
180 {
181     LPSTR       tmp;
182
183     if ((tmp = strrchr(fileName, '.'))) {
184         GetProfileStringA("mci extensions", tmp + 1, "*", buf, len);
185         if (strcmp(buf, "*") != 0) {
186             return 0;
187         }
188         TRACE("No [mci extensions] entry for '%s' found.\n", tmp);
189     }
190     return MCIERR_EXTENSION_NOT_FOUND;
191 }
192
193 #define MAX_MCICMDTABLE                 20
194 #define MCI_COMMAND_TABLE_NOT_LOADED    0xFFFE
195
196 typedef struct tagWINE_MCICMDTABLE {
197     HANDLE              hMem;
198     UINT                uDevType;
199     LPCSTR              lpTable;
200     UINT                nVerbs;         /* number of verbs in command table */
201     LPCSTR*             aVerbs;         /* array of verbs to speed up the verb look up process */
202 } WINE_MCICMDTABLE, *LPWINE_MCICMDTABLE;
203 WINE_MCICMDTABLE        S_MciCmdTable[MAX_MCICMDTABLE];
204
205 /**************************************************************************
206  *                              MCI_IsCommandTableValid         [internal]
207  */
208 static  BOOL            MCI_IsCommandTableValid(UINT uTbl)
209 {
210     LPCSTR      lmem, str;
211     DWORD       flg;
212     WORD        eid;
213     int         idx = 0;
214     BOOL        inCst = FALSE;
215
216     TRACE("Dumping cmdTbl=%d [hMem=%08x devType=%d]\n",
217           uTbl, S_MciCmdTable[uTbl].hMem, S_MciCmdTable[uTbl].uDevType);
218
219     if (uTbl >= MAX_MCICMDTABLE || !S_MciCmdTable[uTbl].hMem || !S_MciCmdTable[uTbl].lpTable)
220         return FALSE;
221
222     lmem = S_MciCmdTable[uTbl].lpTable;
223     do {
224         do {
225             str = lmem;
226             lmem += strlen(lmem) + 1;
227             flg = *(LPDWORD)lmem;
228             eid = *(LPWORD)(lmem + sizeof(DWORD));
229             lmem += sizeof(DWORD) + sizeof(WORD);
230             idx ++;
231             /* EPP          TRACE("cmd='%s' %08lx %04x\n", str, flg, eid); */
232             switch (eid) {
233             case MCI_COMMAND_HEAD:      if (!*str || !flg) return FALSE; idx = 0;                       break;  /* check unicity of str in table */
234             case MCI_STRING:            if (inCst) return FALSE;                                        break;
235             case MCI_INTEGER:           if (!*str) return FALSE;                                        break;
236             case MCI_END_COMMAND:       if (*str || flg || idx == 0) return FALSE; idx = 0;             break;
237             case MCI_RETURN:            if (*str || idx != 1) return FALSE;                             break;
238             case MCI_FLAG:              if (!*str) return FALSE;                                        break;
239             case MCI_END_COMMAND_LIST:  if (*str || flg) return FALSE;  idx = 0;                        break;
240             case MCI_RECT:              if (!*str || inCst) return FALSE;                               break;
241             case MCI_CONSTANT:          if (inCst) return FALSE; inCst = TRUE;                          break;
242             case MCI_END_CONSTANT:      if (*str || flg || !inCst) return FALSE; inCst = FALSE;         break;
243             default:                    return FALSE;
244             }
245         } while (eid != MCI_END_COMMAND_LIST);
246     } while (eid != MCI_END_COMMAND_LIST);
247     return TRUE;
248 }
249
250 /**************************************************************************
251  *                              MCI_DumpCommandTable            [internal]
252  */
253 static  BOOL            MCI_DumpCommandTable(UINT uTbl)
254 {
255     LPCSTR      lmem;
256     LPCSTR      str;
257     DWORD       flg;
258     WORD        eid;
259
260     if (!MCI_IsCommandTableValid(uTbl)) {
261         ERR("Ooops: %d is not valid\n", uTbl);
262         return FALSE;
263     }
264
265     lmem = S_MciCmdTable[uTbl].lpTable;
266     do {
267         do {
268             str = lmem;
269             lmem += strlen(lmem) + 1;
270             flg = *(LPDWORD)lmem;
271             eid = *(LPWORD)(lmem + sizeof(DWORD));
272             TRACE("cmd='%s' %08lx %04x\n", str, flg, eid);
273             lmem += sizeof(DWORD) + sizeof(WORD);
274         } while (eid != MCI_END_COMMAND && eid != MCI_END_COMMAND_LIST);
275         TRACE(" => end of command%s\n", (eid == MCI_END_COMMAND_LIST) ? " list" : "");
276     } while (eid != MCI_END_COMMAND_LIST);
277     return TRUE;
278 }
279
280 static  UINT            MCI_SetCommandTable(HANDLE hMem, UINT uDevType);
281
282 /**************************************************************************
283  *                              MCI_GetCommandTable             [internal]
284  */
285 static  UINT            MCI_GetCommandTable(UINT uDevType)
286 {
287     UINT        uTbl;
288     char        buf[32];
289     LPSTR       str = NULL;
290
291     /* first look up existing for existing devType */
292     for (uTbl = 0; uTbl < MAX_MCICMDTABLE; uTbl++) {
293         if (S_MciCmdTable[uTbl].hMem && S_MciCmdTable[uTbl].uDevType == uDevType)
294             return uTbl;
295     }
296
297     /* well try to load id */
298     if (uDevType >= MCI_DEVTYPE_FIRST && uDevType <= MCI_DEVTYPE_LAST) {
299         if (LoadStringA(WINMM_IData->hWinMM32Instance, uDevType, buf, sizeof(buf))) {
300             str = buf;
301         }
302     } else if (uDevType == 0) {
303         str = "CORE";
304     }
305     uTbl = MCI_NO_COMMAND_TABLE;
306     if (str) {
307         HRSRC   hRsrc = FindResourceA(WINMM_IData->hWinMM32Instance, str, (LPCSTR)RT_RCDATAA);
308         HANDLE  hMem = 0;
309
310         if (hRsrc) hMem = LoadResource(WINMM_IData->hWinMM32Instance, hRsrc);
311         if (hMem) {
312             uTbl = MCI_SetCommandTable(hMem, uDevType);
313         } else {
314             WARN("No command table found in resource %04x[%s]\n",
315                  WINMM_IData->hWinMM32Instance, str);
316         }
317     }
318     TRACE("=> %d\n", uTbl);
319     return uTbl;
320 }
321
322 /**************************************************************************
323  *                              MCI_SetCommandTable             [internal]
324  */
325 static  UINT            MCI_SetCommandTable(HANDLE hMem, UINT uDevType)
326 {
327     int                 uTbl;
328     static      BOOL    bInitDone = FALSE;
329
330     /* <HACK>
331      * The CORE command table must be loaded first, so that MCI_GetCommandTable()
332      * can be called with 0 as a uDevType to retrieve it.
333      * </HACK>
334      */
335     if (!bInitDone) {
336         bInitDone = TRUE;
337         for (uTbl = 0; uTbl < MAX_MCICMDTABLE; uTbl++) {
338             S_MciCmdTable[uTbl].hMem = 0;
339         }
340         MCI_GetCommandTable(0);
341     }
342
343     for (uTbl = 0; uTbl < MAX_MCICMDTABLE; uTbl++) {
344         if (S_MciCmdTable[uTbl].hMem == 0) {
345             LPCSTR      lmem, str;
346             WORD        eid;
347             WORD        count;
348
349             S_MciCmdTable[uTbl].hMem = hMem;
350             S_MciCmdTable[uTbl].uDevType = uDevType;
351             S_MciCmdTable[uTbl].lpTable = LockResource(hMem);
352
353             if (TRACE_ON(mci)) {
354                 MCI_DumpCommandTable(uTbl);
355             }
356
357             /* create the verbs table */
358             /* get # of entries */
359             lmem = S_MciCmdTable[uTbl].lpTable;
360             count = 0;
361             do {
362                 lmem += strlen(lmem) + 1;
363                 eid = *(LPWORD)(lmem + sizeof(DWORD));
364                 lmem += sizeof(DWORD) + sizeof(WORD);
365                 if (eid == MCI_COMMAND_HEAD)
366                     count++;
367             } while (eid != MCI_END_COMMAND_LIST);
368
369             S_MciCmdTable[uTbl].aVerbs = HeapAlloc(GetProcessHeap(), 0, count * sizeof(LPCSTR));
370             S_MciCmdTable[uTbl].nVerbs = count;
371
372             lmem = S_MciCmdTable[uTbl].lpTable;
373             count = 0;
374             do {
375                 str = lmem;
376                 lmem += strlen(lmem) + 1;
377                 eid = *(LPWORD)(lmem + sizeof(DWORD));
378                 lmem += sizeof(DWORD) + sizeof(WORD);
379                 if (eid == MCI_COMMAND_HEAD)
380                     S_MciCmdTable[uTbl].aVerbs[count++] = str;
381             } while (eid != MCI_END_COMMAND_LIST);
382             /* assert(count == S_MciCmdTable[uTbl].nVerbs); */
383             return uTbl;
384         }
385     }
386
387     return MCI_NO_COMMAND_TABLE;
388 }
389
390 /**************************************************************************
391  *                              MCI_DeleteCommandTable          [internal]
392  */
393 static  BOOL    MCI_DeleteCommandTable(UINT uTbl)
394 {
395     if (uTbl >= MAX_MCICMDTABLE || !S_MciCmdTable[uTbl].hMem)
396         return FALSE;
397
398     FreeResource(S_MciCmdTable[uTbl].hMem);
399     S_MciCmdTable[uTbl].hMem = 0;
400     if (S_MciCmdTable[uTbl].aVerbs) {
401         HeapFree(GetProcessHeap(), 0, S_MciCmdTable[uTbl].aVerbs);
402         S_MciCmdTable[uTbl].aVerbs = 0;
403     }
404     return TRUE;
405 }
406
407 /**************************************************************************
408  *                              MCI_UnLoadMciDriver             [internal]
409  */
410 static  BOOL    MCI_UnLoadMciDriver(LPWINE_MCIDRIVER wmd)
411 {
412     LPWINE_MCIDRIVER*           tmp;
413
414     if (!wmd)
415         return TRUE;
416
417     CloseDriver(wmd->hDriver, 0, 0);
418
419     if (wmd->dwPrivate != 0)
420         WARN("Unloading mci driver with non nul dwPrivate field\n");
421
422     EnterCriticalSection(&WINMM_IData->cs);
423     for (tmp = &WINMM_IData->lpMciDrvs; *tmp; tmp = &(*tmp)->lpNext) {
424         if (*tmp == wmd) {
425             *tmp = wmd->lpNext;
426             break;
427         }
428     }
429     LeaveCriticalSection(&WINMM_IData->cs);
430
431     HeapFree(GetProcessHeap(), 0, wmd->lpstrDeviceType);
432     HeapFree(GetProcessHeap(), 0, wmd->lpstrAlias);
433     HeapFree(GetProcessHeap(), 0, wmd->lpstrElementName);
434
435     HeapFree(GetProcessHeap(), 0, wmd);
436     return TRUE;
437 }
438
439 /**************************************************************************
440  *                              MCI_OpenMciDriver               [internal]
441  */
442 static  BOOL    MCI_OpenMciDriver(LPWINE_MCIDRIVER wmd, LPCSTR drvTyp, LPARAM lp)
443 {
444     char        libName[128];
445
446     if (!DRIVER_GetLibName(drvTyp, "mci", libName, sizeof(libName)))
447         return FALSE;
448
449     wmd->bIs32 = 0xFFFF;
450     /* First load driver */
451     if ((wmd->hDriver = (HDRVR)DRIVER_TryOpenDriver32(libName, lp))) {
452         wmd->bIs32 = TRUE;
453     } else {
454         WINMM_MapType   res;
455
456         switch (res = MCI_MapMsg32ATo16(0, DRV_OPEN, 0, &lp)) {
457         case WINMM_MAP_MSGERROR:
458             TRACE("Not handled yet (DRV_OPEN)\n");
459             break;
460         case WINMM_MAP_NOMEM:
461             TRACE("Problem mapping msg=DRV_OPEN from 32a to 16\n");
462             break;
463         case WINMM_MAP_OK:
464         case WINMM_MAP_OKMEM:
465             if ((wmd->hDriver = OpenDriverA(drvTyp, "mci", lp)))
466                 wmd->bIs32 = FALSE;
467             if (res == WINMM_MAP_OKMEM)
468                 MCI_UnMapMsg32ATo16(0, DRV_OPEN, 0, lp);
469             break;
470         }
471     }
472     return (wmd->bIs32 == 0xFFFF) ? FALSE : TRUE;
473 }
474
475 /**************************************************************************
476  *                              MCI_LoadMciDriver               [internal]
477  */
478 static  DWORD   MCI_LoadMciDriver(LPCSTR _strDevTyp, LPWINE_MCIDRIVER* lpwmd)
479 {
480     LPSTR                       strDevTyp = str_dup_upper(_strDevTyp);
481     LPWINE_MCIDRIVER            wmd = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(*wmd));
482     MCI_OPEN_DRIVER_PARMSA      modp;
483     DWORD                       dwRet = 0;
484
485     if (!wmd || !strDevTyp) {
486         dwRet = MCIERR_OUT_OF_MEMORY;
487         goto errCleanUp;
488     }
489
490     wmd->lpfnYieldProc = MCI_DefYieldProc;
491     wmd->dwYieldData = VK_CANCEL;
492     wmd->hCreatorTask = GetCurrentTask();
493     wmd->CreatorThread = GetCurrentThreadId();
494
495     EnterCriticalSection(&WINMM_IData->cs);
496     /* wmd must be inserted in list before sending opening the driver, coz' it
497      * may want to lookup at wDevID
498      */
499     wmd->lpNext = WINMM_IData->lpMciDrvs;
500     WINMM_IData->lpMciDrvs = wmd;
501
502     for (modp.wDeviceID = MCI_MAGIC;
503          MCI_GetDriver(modp.wDeviceID) != 0;
504          modp.wDeviceID++);
505
506     wmd->wDeviceID = modp.wDeviceID;
507
508     LeaveCriticalSection(&WINMM_IData->cs);
509
510     TRACE("wDevID=%04X \n", modp.wDeviceID);
511
512     modp.lpstrParams = NULL;
513
514     if (!MCI_OpenMciDriver(wmd, strDevTyp, (LPARAM)&modp)) {
515         /* silence warning if all is used... some bogus program use commands like
516          * 'open all'...
517          */
518         if (strcasecmp(strDevTyp, "all") != 0) {
519             dwRet = MCIERR_CANNOT_USE_ALL;
520         } else {
521             FIXME("Couldn't load driver for type %s.\n"
522                   "If you don't have a windows installation accessible from Wine,\n"
523                   "you perhaps forgot to create a [mci] section in system.ini\n",
524                   strDevTyp);
525             dwRet = MCIERR_DEVICE_NOT_INSTALLED;
526         }
527         goto errCleanUp;
528     }
529
530     /* FIXME: should also check that module's description is of the form
531      * MODULENAME:[MCI] comment
532      */
533
534     /* some drivers will return 0x0000FFFF, some others 0xFFFFFFFF */
535     wmd->uSpecificCmdTable = LOWORD(modp.wCustomCommandTable);
536     wmd->uTypeCmdTable = MCI_COMMAND_TABLE_NOT_LOADED;
537
538     TRACE("Loaded driver %x (%s), type is %d, cmdTable=%08x\n",
539           wmd->hDriver, strDevTyp, modp.wType, modp.wCustomCommandTable);
540
541     wmd->lpstrDeviceType = strDevTyp;
542     wmd->wType = modp.wType;
543
544     TRACE("mcidev=%d, uDevTyp=%04X wDeviceID=%04X !\n",
545           modp.wDeviceID, modp.wType, modp.wDeviceID);
546     *lpwmd = wmd;
547     return 0;
548 errCleanUp:
549     MCI_UnLoadMciDriver(wmd);
550     HeapFree(GetProcessHeap(), 0, strDevTyp);
551     *lpwmd = 0;
552     return dwRet;
553 }
554
555 /**************************************************************************
556  *                      MCI_FinishOpen                          [internal]
557  */
558 static  DWORD   MCI_FinishOpen(LPWINE_MCIDRIVER wmd, LPMCI_OPEN_PARMSA lpParms,
559                                DWORD dwParam)
560 {
561     if (dwParam & MCI_OPEN_ELEMENT)
562     {
563         wmd->lpstrElementName = HeapAlloc(GetProcessHeap(),0,strlen(lpParms->lpstrElementName)+1);
564         strcpy( wmd->lpstrElementName, lpParms->lpstrElementName );
565     }
566     if (dwParam & MCI_OPEN_ALIAS)
567     {
568         wmd->lpstrAlias = HeapAlloc(GetProcessHeap(), 0, strlen(lpParms->lpstrAlias)+1);
569         strcpy( wmd->lpstrAlias, lpParms->lpstrAlias);
570     }
571     lpParms->wDeviceID = wmd->wDeviceID;
572
573     return MCI_SendCommandFrom32(wmd->wDeviceID, MCI_OPEN_DRIVER, dwParam,
574                                  (DWORD)lpParms);
575 }
576
577 /**************************************************************************
578  *                              MCI_FindCommand         [internal]
579  */
580 static  LPCSTR          MCI_FindCommand(UINT uTbl, LPCSTR verb)
581 {
582     UINT        idx;
583
584     if (uTbl >= MAX_MCICMDTABLE || S_MciCmdTable[uTbl].hMem == 0)
585         return NULL;
586
587     /* another improvement would be to have the aVerbs array sorted,
588      * so that we could use a dichotomic search on it, rather than this dumb
589      * array look up
590      */
591     for (idx = 0; idx < S_MciCmdTable[uTbl].nVerbs; idx++) {
592         if (strcmp(S_MciCmdTable[uTbl].aVerbs[idx], verb) == 0)
593             return S_MciCmdTable[uTbl].aVerbs[idx];
594     }
595
596     return NULL;
597 }
598
599 /**************************************************************************
600  *                              MCI_GetReturnType               [internal]
601  */
602 static  DWORD           MCI_GetReturnType(LPCSTR lpCmd)
603 {
604     lpCmd += strlen(lpCmd) + 1 + sizeof(DWORD) + sizeof(WORD);
605     if (*lpCmd == '\0' && *(LPWORD)(lpCmd + 1 + sizeof(DWORD)) == MCI_RETURN) {
606         return *(LPDWORD)(lpCmd + 1);
607     }
608     return 0L;
609 }
610
611 /**************************************************************************
612  *                              MCI_GetMessage                  [internal]
613  */
614 static  WORD            MCI_GetMessage(LPCSTR lpCmd)
615 {
616     return (WORD)*(LPDWORD)(lpCmd + strlen(lpCmd) + 1);
617 }
618
619 /**************************************************************************
620  *                              MCI_GetDWord                    [internal]
621  */
622 static  BOOL            MCI_GetDWord(LPDWORD data, LPSTR* ptr)
623 {
624     DWORD       val;
625     LPSTR       ret;
626
627     val = strtoul(*ptr, &ret, 0);
628
629     switch (*ret) {
630     case '\0':  break;
631     case ' ':   ret++; break;
632     default:    return FALSE;
633     }
634
635     *data |= val;
636     *ptr = ret;
637     return TRUE;
638 }
639
640 /**************************************************************************
641  *                              MCI_GetString           [internal]
642  */
643 static  DWORD   MCI_GetString(LPSTR* str, LPSTR* args)
644 {
645     LPSTR       ptr = *args;
646
647     /* see if we have a quoted string */
648     if (*ptr == '"') {
649         ptr = strchr(*str = ptr + 1, '"');
650         if (!ptr) return MCIERR_NO_CLOSING_QUOTE;
651         /* FIXME: shall we escape \" from string ?? */
652         if (ptr[-1] == '\\') TRACE("Ooops: un-escaped \"\n");
653         *ptr++ = '\0'; /* remove trailing " */
654         if (*ptr != ' ' && *ptr != '\0') return MCIERR_EXTRA_CHARACTERS;
655         *ptr++ = '\0';
656     } else {
657         ptr = strchr(ptr, ' ');
658
659         if (ptr) {
660             *ptr++ = '\0';
661         } else {
662             ptr = *args + strlen(*args);
663         }
664         *str = *args;
665     }
666
667     *args = ptr;
668     return 0;
669 }
670
671 #define MCI_DATA_SIZE   16
672
673 /**************************************************************************
674  *                              MCI_ParseOptArgs                [internal]
675  */
676 static  DWORD   MCI_ParseOptArgs(LPDWORD data, int _offset, LPCSTR lpCmd,
677                                  LPSTR args, LPDWORD dwFlags)
678 {
679     int         len, offset;
680     LPCSTR      lmem, str;
681     DWORD       dwRet, flg, cflg = 0;
682     WORD        eid;
683     BOOL        inCst, found;
684
685     /* loop on arguments */
686     while (*args) {
687         lmem = lpCmd;
688         found = inCst = FALSE;
689         offset = _offset;
690
691         /* skip any leading white space(s) */
692         while (*args == ' ') args++;
693         TRACE("args='%s' offset=%d\n", args, offset);
694
695         do { /* loop on options for command table for the requested verb */
696             str = lmem;
697             lmem += (len = strlen(lmem)) + 1;
698             flg = *(LPDWORD)lmem;
699             eid = *(LPWORD)(lmem + sizeof(DWORD));
700             lmem += sizeof(DWORD) + sizeof(WORD);
701 /* EPP      TRACE("\tcmd='%s' inCst=%c eid=%04x\n", str, inCst ? 'Y' : 'N', eid); */
702
703             switch (eid) {
704             case MCI_CONSTANT:
705                 inCst = TRUE;   cflg = flg;     break;
706             case MCI_END_CONSTANT:
707                 /* there may be additional integral values after flag in constant */
708                 if (inCst && MCI_GetDWord(&(data[offset]), &args)) {
709                     *dwFlags |= cflg;
710                 }
711                 inCst = FALSE;  cflg = 0;
712                 break;
713             }
714
715             if (strncasecmp(args, str, len) == 0 &&
716                 (args[len] == 0 || args[len] == ' ')) {
717                 /* store good values into data[] */
718                 args += len;
719                 while (*args == ' ') args++;
720                 found = TRUE;
721
722                 switch (eid) {
723                 case MCI_COMMAND_HEAD:
724                 case MCI_RETURN:
725                 case MCI_END_COMMAND:
726                 case MCI_END_COMMAND_LIST:
727                 case MCI_CONSTANT:      /* done above */
728                 case MCI_END_CONSTANT:  /* done above */
729                     break;
730                 case MCI_FLAG:
731                     *dwFlags |= flg;
732                     break;
733                 case MCI_INTEGER:
734                     if (inCst) {
735                         data[offset] |= flg;
736                         *dwFlags |= cflg;
737                         inCst = FALSE;
738                     } else {
739                         *dwFlags |= flg;
740                         if (!MCI_GetDWord(&(data[offset]), &args)) {
741                             return MCIERR_BAD_INTEGER;
742                         }
743                     }
744                     break;
745                 case MCI_RECT:
746                     /* store rect in data (offset...offset+3) */
747                     *dwFlags |= flg;
748                     if (!MCI_GetDWord(&(data[offset+0]), &args) ||
749                         !MCI_GetDWord(&(data[offset+1]), &args) ||
750                         !MCI_GetDWord(&(data[offset+2]), &args) ||
751                         !MCI_GetDWord(&(data[offset+3]), &args)) {
752                         ERR("Bad rect '%s'\n", args);
753                         return MCIERR_BAD_INTEGER;
754                     }
755                     break;
756                 case MCI_STRING:
757                     *dwFlags |= flg;
758                     if ((dwRet = MCI_GetString((LPSTR*)&data[offset], &args)))
759                         return dwRet;
760                     break;
761                 default:        ERR("oops\n");
762                 }
763                 /* exit inside while loop, except if just entered in constant area definition */
764                 if (!inCst || eid != MCI_CONSTANT) eid = MCI_END_COMMAND;
765             } else {
766                 /* have offset incremented if needed */
767                 switch (eid) {
768                 case MCI_COMMAND_HEAD:
769                 case MCI_RETURN:
770                 case MCI_END_COMMAND:
771                 case MCI_END_COMMAND_LIST:
772                 case MCI_CONSTANT:
773                 case MCI_FLAG:                  break;
774                 case MCI_INTEGER:               if (!inCst) offset++;   break;
775                 case MCI_END_CONSTANT:
776                 case MCI_STRING:                offset++; break;
777                 case MCI_RECT:                  offset += 4; break;
778                 default:                        ERR("oops\n");
779                 }
780             }
781         } while (eid != MCI_END_COMMAND);
782         if (!found) {
783             WARN("Optarg '%s' not found\n", args);
784             return MCIERR_UNRECOGNIZED_COMMAND;
785         }
786         if (offset == MCI_DATA_SIZE) {
787             ERR("Internal data[] buffer overflow\n");
788             return MCIERR_PARSER_INTERNAL;
789         }
790     }
791     return 0;
792 }
793
794 /**************************************************************************
795  *                              MCI_HandleReturnValues  [internal]
796  */
797 static  DWORD   MCI_HandleReturnValues(DWORD dwRet, LPWINE_MCIDRIVER wmd, DWORD retType, 
798                                        LPDWORD data, LPSTR lpstrRet, UINT uRetLen)
799 {
800     if (lpstrRet) {
801         switch (retType) {
802         case 0: /* nothing to return */
803             break;
804         case MCI_INTEGER:
805             switch (dwRet & 0xFFFF0000ul) {
806             case 0:
807             case MCI_INTEGER_RETURNED:
808                 snprintf(lpstrRet, uRetLen, "%ld", data[1]);
809                 break;
810             case MCI_RESOURCE_RETURNED:
811                 /* return string which ID is HIWORD(data[1]),
812                  * string is loaded from mmsystem.dll */
813                 LoadStringA(WINMM_IData->hWinMM32Instance, HIWORD(data[1]),
814                             lpstrRet, uRetLen);
815                 break;
816             case MCI_RESOURCE_RETURNED|MCI_RESOURCE_DRIVER:
817                 /* return string which ID is HIWORD(data[1]),
818                  * string is loaded from driver */
819                 /* FIXME: this is wrong for a 16 bit handle */
820                 LoadStringA(GetDriverModuleHandle(wmd->hDriver),
821                             HIWORD(data[1]), lpstrRet, uRetLen);
822                 break;
823             case MCI_COLONIZED3_RETURN:
824                 snprintf(lpstrRet, uRetLen, "%d:%d:%d",
825                          LOBYTE(LOWORD(data[1])), HIBYTE(LOWORD(data[1])),
826                          LOBYTE(HIWORD(data[1])));
827                 break;
828             case MCI_COLONIZED4_RETURN:
829                 snprintf(lpstrRet, uRetLen, "%d:%d:%d:%d",
830                          LOBYTE(LOWORD(data[1])), HIBYTE(LOWORD(data[1])),
831                          LOBYTE(HIWORD(data[1])), HIBYTE(HIWORD(data[1])));
832                 break;
833             default:    ERR("Ooops (%04X)\n", HIWORD(dwRet));
834             }
835             break;
836         case MCI_STRING:
837             switch (dwRet & 0xFFFF0000ul) {
838             case 0:
839                 /* nothing to do data[1] == lpstrRet */
840                 break;
841             case MCI_INTEGER_RETURNED:
842                 data[1] = *(LPDWORD)lpstrRet;
843                 snprintf(lpstrRet, uRetLen, "%ld", data[1]);
844                 break;
845             default:
846                 WARN("Oooch. MCI_STRING and HIWORD(dwRet)=%04x\n", HIWORD(dwRet));
847                 break;
848             }
849             break;
850         case MCI_RECT:
851             if (dwRet & 0xFFFF0000ul)
852                 WARN("Oooch. MCI_STRING and HIWORD(dwRet)=%04x\n", HIWORD(dwRet));
853             snprintf(lpstrRet, uRetLen, "%ld %ld %ld %ld",
854                        data[1], data[2], data[3], data[4]);
855             break;
856         default:                ERR("oops\n");
857         }
858     }
859     return LOWORD(dwRet);
860 }
861
862 /**************************************************************************
863  *                              mciSendStringA          [WINMM.@]
864  */
865 DWORD WINAPI mciSendStringA(LPCSTR lpstrCommand, LPSTR lpstrRet,
866                             UINT uRetLen, HWND hwndCallback)
867 {
868     LPSTR               verb, dev, args;
869     LPWINE_MCIDRIVER    wmd = 0;
870     DWORD               dwFlags = 0, dwRet = 0;
871     int                 offset = 0;
872     DWORD               data[MCI_DATA_SIZE];
873     DWORD               retType;
874     LPCSTR              lpCmd = 0;
875     LPSTR               devAlias = NULL;
876     BOOL                bAutoOpen = FALSE;
877
878     TRACE("('%s', %p, %d, %X)\n", lpstrCommand, lpstrRet, uRetLen, hwndCallback);
879
880     /* format is <command> <device> <optargs> */
881     if (!(verb = HeapAlloc(GetProcessHeap(), 0, strlen(lpstrCommand)+1)))
882         return MCIERR_OUT_OF_MEMORY;
883     strcpy( verb, lpstrCommand );
884
885     memset(data, 0, sizeof(data));
886
887     if (!(args = strchr(verb, ' '))) {
888         dwRet = MCIERR_MISSING_DEVICE_NAME;
889         goto errCleanUp;
890     }
891     *args++ = '\0';
892     if ((dwRet = MCI_GetString(&dev, &args))) {
893         goto errCleanUp;
894     }
895
896     /* case dev == 'new' has to be handled */
897     if (!strcasecmp(dev, "new")) {
898         FIXME("'new': NIY as device name\n");
899         dwRet = MCIERR_MISSING_DEVICE_NAME;
900         goto errCleanUp;
901     }
902
903     /* otherwise, try to grab devType from open */
904     if (!strcmp(verb, "open")) {
905         LPSTR   devType, tmp;
906
907         if ((devType = strchr(dev, '!')) != NULL) {
908             *devType++ = '\0';
909             tmp = devType; devType = dev; dev = tmp;
910
911             dwFlags |= MCI_OPEN_TYPE;
912             data[2] = (DWORD)devType;
913             devType = str_dup_upper(devType);
914             dwFlags |= MCI_OPEN_ELEMENT;
915             data[3] = (DWORD)dev;
916         } else if (strchr(dev, '.') == NULL) {
917             tmp = strchr(dev,' ');
918             if (tmp) *tmp = '\0';
919             data[2] = (DWORD)dev;
920             devType = str_dup_upper(dev);
921             if (tmp) *tmp = ' ';
922             dwFlags |= MCI_OPEN_TYPE;
923         } else {
924             if ((devType = strstr(args, "type ")) != NULL) {
925                 devType += 5;
926                 tmp = strchr(devType, ' ');
927                 if (tmp) *tmp = '\0';
928                 devType = str_dup_upper(devType);
929                 if (tmp) *tmp = ' ';
930                 /* dwFlags and data[2] will be correctly set in ParseOpt loop */
931             } else {
932                 char    buf[32];
933                 if ((dwRet = MCI_GetDevTypeFromFileName(dev, buf, sizeof(buf))))
934                     goto errCleanUp;
935
936                 devType = str_dup_upper(buf);
937             }
938             dwFlags |= MCI_OPEN_ELEMENT;
939             data[3] = (DWORD)dev;
940         }
941         if ((devAlias = strstr(args," alias "))) {
942             char *tmp2;
943             devAlias += 7;
944             if (!(tmp = strchr(devAlias,' '))) tmp = devAlias + strlen(devAlias);
945             if (tmp) *tmp = '\0';
946             tmp2 = HeapAlloc(GetProcessHeap(), 0, tmp - devAlias + 1 );
947             memcpy( tmp2, devAlias, tmp - devAlias );
948             tmp2[tmp - devAlias] = 0;
949             data[4] = (DWORD)tmp2;
950             /* should be done in regular options parsing */
951             /* dwFlags |= MCI_OPEN_ALIAS; */
952         }
953
954         dwRet = MCI_LoadMciDriver(devType, &wmd);
955         HeapFree(GetProcessHeap(), 0, devType);
956         if (dwRet) {
957             MCI_UnLoadMciDriver(wmd);
958             goto errCleanUp;
959         }
960     } else if (!(wmd = MCI_GetDriver(mciGetDeviceIDA(dev)))) {
961         /* auto open */
962         char    buf[128];
963         sprintf(buf, "open %s wait", dev);
964
965         if ((dwRet = mciSendStringA(buf, NULL, 0, 0)) != 0)
966             goto errCleanUp;
967
968         wmd = MCI_GetDriver(mciGetDeviceIDA(dev));
969         if (!wmd) {
970             /* FIXME: memory leak, MCI driver is not closed */
971             dwRet = MCIERR_INVALID_DEVICE_ID;
972             goto errCleanUp;
973         }
974     }
975
976     /* get the verb in the different command tables */
977     if (wmd) {
978         /* try the device specific command table */
979         lpCmd = MCI_FindCommand(wmd->uSpecificCmdTable, verb);
980         if (!lpCmd) {
981             /* try the type specific command table */
982             if (wmd->uTypeCmdTable == MCI_COMMAND_TABLE_NOT_LOADED)
983                 wmd->uTypeCmdTable = MCI_GetCommandTable(wmd->wType);
984             if (wmd->uTypeCmdTable != MCI_NO_COMMAND_TABLE)
985                 lpCmd = MCI_FindCommand(wmd->uTypeCmdTable, verb);
986         }
987     }
988     /* try core command table */
989     if (!lpCmd) lpCmd = MCI_FindCommand(MCI_GetCommandTable(0), verb);
990
991     if (!lpCmd) {
992         TRACE("Command '%s' not found!\n", verb);
993         dwRet = MCIERR_UNRECOGNIZED_COMMAND;
994         goto errCleanUp;
995     }
996
997     /* set up call back */
998     if (hwndCallback != 0) {
999         dwFlags |= MCI_NOTIFY;
1000         data[0] = (DWORD)hwndCallback;
1001     }
1002
1003     /* set return information */
1004     switch (retType = MCI_GetReturnType(lpCmd)) {
1005     case 0:             offset = 1;     break;
1006     case MCI_INTEGER:   offset = 2;     break;
1007     case MCI_STRING:    data[1] = (DWORD)lpstrRet; data[2] = uRetLen; offset = 3; break;
1008     case MCI_RECT:      offset = 5;     break;
1009     default:    ERR("oops\n");
1010     }
1011
1012     TRACE("verb='%s' on dev='%s'; offset=%d\n", verb, dev, offset);
1013
1014     if ((dwRet = MCI_ParseOptArgs(data, offset, lpCmd, args, &dwFlags)))
1015         goto errCleanUp;
1016
1017     if (bAutoOpen && (dwFlags & MCI_NOTIFY)) {
1018         dwRet = MCIERR_NOTIFY_ON_AUTO_OPEN;
1019         goto errCleanUp;
1020     }
1021     /* FIXME: the command should get it's own notification window set up and
1022      * ask for device closing while processing the notification mechanism
1023      */
1024     if (lpstrRet && uRetLen) *lpstrRet = '\0';
1025
1026 #define STR_OF(_x) (IsBadReadPtr((char*)_x,1)?"?":(char*)(_x))
1027     TRACE("[%d, %s, %08lx, %08lx/%s %08lx/%s %08lx/%s %08lx/%s %08lx/%s %08lx/%s]\n",
1028           wmd->wDeviceID, MCI_MessageToString(MCI_GetMessage(lpCmd)), dwFlags,
1029           data[0], STR_OF(data[0]), data[1], STR_OF(data[1]),
1030           data[2], STR_OF(data[2]), data[3], STR_OF(data[3]),
1031           data[4], STR_OF(data[4]), data[5], STR_OF(data[5]));
1032 #undef STR_OF
1033
1034     if (strcmp(verb, "open") == 0) {
1035         if ((dwRet = MCI_FinishOpen(wmd, (LPMCI_OPEN_PARMSA)data, dwFlags)))
1036             MCI_UnLoadMciDriver(wmd);
1037         /* FIXME: notification is not properly shared across two opens */
1038     } else {
1039         dwRet = MCI_SendCommand(wmd->wDeviceID, MCI_GetMessage(lpCmd), dwFlags, (DWORD)data, TRUE);
1040     }
1041     TRACE("=> 1/ %lx (%s)\n", dwRet, lpstrRet);
1042     dwRet = MCI_HandleReturnValues(dwRet, wmd, retType, data, lpstrRet, uRetLen);
1043     TRACE("=> 2/ %lx (%s)\n", dwRet, lpstrRet);
1044
1045 errCleanUp:
1046     HeapFree(GetProcessHeap(), 0, verb);
1047     HeapFree(GetProcessHeap(), 0, devAlias);
1048     return dwRet;
1049 }
1050
1051 /**************************************************************************
1052  *                              mciSendStringW                  [WINMM.@]
1053  */
1054 DWORD WINAPI mciSendStringW(LPCWSTR lpwstrCommand, LPSTR lpstrRet,
1055                             UINT uRetLen, HWND hwndCallback)
1056 {
1057     LPSTR       lpstrCommand;
1058     UINT        ret;
1059     INT len;
1060
1061     /* FIXME: is there something to do with lpstrReturnString ? */
1062     len = WideCharToMultiByte( CP_ACP, 0, lpwstrCommand, -1, NULL, 0, NULL, NULL );
1063     lpstrCommand = HeapAlloc( GetProcessHeap(), 0, len );
1064     WideCharToMultiByte( CP_ACP, 0, lpwstrCommand, -1, lpstrCommand, len, NULL, NULL );
1065     ret = mciSendStringA(lpstrCommand, lpstrRet, uRetLen, hwndCallback);
1066     HeapFree(GetProcessHeap(), 0, lpstrCommand);
1067     return ret;
1068 }
1069
1070 /**************************************************************************
1071  *                              mciExecute                      [WINMM.@]
1072  *                              mciExecute                      [MMSYSTEM.712]
1073  */
1074 DWORD WINAPI mciExecute(LPCSTR lpstrCommand)
1075 {
1076     char        strRet[256];
1077     DWORD       ret;
1078
1079     TRACE("(%s)!\n", lpstrCommand);
1080
1081     ret = mciSendStringA(lpstrCommand, strRet, sizeof(strRet), 0);
1082     if (ret != 0) {
1083         if (!mciGetErrorStringA(ret, strRet, sizeof(strRet))) {
1084             sprintf(strRet, "Unknown MCI error (%ld)", ret);
1085         }
1086         MessageBoxA(0, strRet, "Error in mciExecute()", MB_OK);
1087     }
1088     /* FIXME: what shall I return ? */
1089     return 0;
1090 }
1091
1092 /**************************************************************************
1093  *                      mciLoadCommandResource                  [WINMM.@]
1094  *
1095  * Strangely, this function only exists as an UNICODE one.
1096  */
1097 UINT WINAPI mciLoadCommandResource(HINSTANCE hInst, LPCWSTR resNameW, UINT type)
1098 {
1099     HRSRC               hRsrc = 0;
1100     HGLOBAL             hMem;
1101     UINT16              ret = MCI_NO_COMMAND_TABLE;
1102
1103     TRACE("(%04x, %s, %d)!\n", hInst, debugstr_w(resNameW), type);
1104
1105     /* if a file named "resname.mci" exits, then load resource "resname" from it
1106      * otherwise directly from driver
1107      * We don't support it (who uses this feature ?), but we check anyway
1108      */
1109     if (!type) {
1110 #if 0
1111         /* FIXME: we should put this back into order, but I never found a program
1112          * actually using this feature, so we not need it
1113          */
1114         char            buf[128];
1115         OFSTRUCT        ofs;
1116
1117         strcat(strcpy(buf, resname), ".mci");
1118         if (OpenFile(buf, &ofs, OF_EXIST) != HFILE_ERROR) {
1119             FIXME("NIY: command table to be loaded from '%s'\n", ofs.szPathName);
1120         }
1121 #endif
1122     }
1123     if (!(hRsrc = FindResourceW(hInst, resNameW, (LPCWSTR)RT_RCDATAA))) {
1124         WARN("No command table found in resource\n");
1125     } else if ((hMem = LoadResource(hInst, hRsrc))) {
1126         ret = MCI_SetCommandTable(hMem, type);
1127     } else {
1128         WARN("Couldn't load resource.\n");
1129     }
1130     TRACE("=> %04x\n", ret);
1131     return ret;
1132 }
1133
1134 /**************************************************************************
1135  *                      mciFreeCommandResource                  [WINMM.@]
1136  */
1137 BOOL WINAPI mciFreeCommandResource(UINT uTable)
1138 {
1139     TRACE("(%08x)!\n", uTable);
1140
1141     return MCI_DeleteCommandTable(uTable);
1142 }
1143
1144 /**************************************************************************
1145  *                      MCI_SendCommandFrom32                   [internal]
1146  */
1147 DWORD MCI_SendCommandFrom32(UINT wDevID, UINT16 wMsg, DWORD dwParam1, DWORD dwParam2)
1148 {
1149     DWORD               dwRet = MCIERR_INVALID_DEVICE_ID;
1150     LPWINE_MCIDRIVER    wmd = MCI_GetDriver(wDevID);
1151
1152     if (wmd) {
1153         if (wmd->bIs32) {
1154             dwRet = SendDriverMessage(wmd->hDriver, wMsg, dwParam1, dwParam2);
1155         } else {
1156             WINMM_MapType       res;
1157
1158             switch (res = MCI_MapMsg32ATo16(wmd->wType, wMsg, dwParam1, &dwParam2)) {
1159             case WINMM_MAP_MSGERROR:
1160                 TRACE("Not handled yet (%s)\n", MCI_MessageToString(wMsg));
1161                 dwRet = MCIERR_DRIVER_INTERNAL;
1162                 break;
1163             case WINMM_MAP_NOMEM:
1164                 TRACE("Problem mapping msg=%s from 32a to 16\n", MCI_MessageToString(wMsg));
1165                 dwRet = MCIERR_OUT_OF_MEMORY;
1166                 break;
1167             case WINMM_MAP_OK:
1168             case WINMM_MAP_OKMEM:
1169                 dwRet = SendDriverMessage(wmd->hDriver, wMsg, dwParam1, dwParam2);
1170                 if (res == WINMM_MAP_OKMEM)
1171                     MCI_UnMapMsg32ATo16(wmd->wType, wMsg, dwParam1, dwParam2);
1172                 break;
1173             }
1174         }
1175     }
1176     return dwRet;
1177 }
1178
1179 /**************************************************************************
1180  *                      MCI_SendCommandFrom16                   [internal]
1181  */
1182 DWORD MCI_SendCommandFrom16(UINT wDevID, UINT16 wMsg, DWORD dwParam1, DWORD dwParam2)
1183 {
1184     DWORD               dwRet = MCIERR_INVALID_DEVICE_ID;
1185     LPWINE_MCIDRIVER    wmd = MCI_GetDriver(wDevID);
1186
1187     if (wmd) {
1188         dwRet = MCIERR_INVALID_DEVICE_ID;
1189
1190         if (wmd->bIs32) {
1191             WINMM_MapType               res;
1192
1193             switch (res = MCI_MapMsg16To32A(wmd->wType, wMsg, &dwParam2)) {
1194             case WINMM_MAP_MSGERROR:
1195                 TRACE("Not handled yet (%s)\n", MCI_MessageToString(wMsg));
1196                 dwRet = MCIERR_DRIVER_INTERNAL;
1197                 break;
1198             case WINMM_MAP_NOMEM:
1199                 TRACE("Problem mapping msg=%s from 16 to 32a\n", MCI_MessageToString(wMsg));
1200                 dwRet = MCIERR_OUT_OF_MEMORY;
1201                 break;
1202             case WINMM_MAP_OK:
1203             case WINMM_MAP_OKMEM:
1204                 dwRet = SendDriverMessage(wmd->hDriver, wMsg, dwParam1, dwParam2);
1205                 if (res == WINMM_MAP_OKMEM)
1206                     MCI_UnMapMsg16To32A(wmd->wType, wMsg, dwParam2);
1207                 break;
1208             }
1209         } else {
1210             dwRet = SendDriverMessage(wmd->hDriver, wMsg, dwParam1, dwParam2);
1211         }
1212     }
1213     return dwRet;
1214 }
1215
1216 /**************************************************************************
1217  *                      MCI_Open                                [internal]
1218  */
1219 static  DWORD MCI_Open(DWORD dwParam, LPMCI_OPEN_PARMSA lpParms)
1220 {
1221     char                        strDevTyp[128];
1222     DWORD                       dwRet;
1223     LPWINE_MCIDRIVER            wmd = NULL;
1224
1225     TRACE("(%08lX, %p)\n", dwParam, lpParms);
1226     if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
1227
1228     /* only two low bytes are generic, the other ones are dev type specific */
1229 #define WINE_MCIDRIVER_SUPP     (0xFFFF0000|MCI_OPEN_SHAREABLE|MCI_OPEN_ELEMENT| \
1230                          MCI_OPEN_ALIAS|MCI_OPEN_TYPE|MCI_OPEN_TYPE_ID| \
1231                          MCI_NOTIFY|MCI_WAIT)
1232     if ((dwParam & ~WINE_MCIDRIVER_SUPP) != 0) {
1233         FIXME("Unsupported yet dwFlags=%08lX\n", dwParam & ~WINE_MCIDRIVER_SUPP);
1234     }
1235 #undef WINE_MCIDRIVER_SUPP
1236
1237     strDevTyp[0] = 0;
1238
1239     if (dwParam & MCI_OPEN_TYPE) {
1240         if (dwParam & MCI_OPEN_TYPE_ID) {
1241             WORD uDevType = LOWORD((DWORD)lpParms->lpstrDeviceType);
1242
1243             if (uDevType < MCI_DEVTYPE_FIRST ||
1244                 uDevType > MCI_DEVTYPE_LAST ||
1245                 !LoadStringA(WINMM_IData->hWinMM32Instance, uDevType, strDevTyp, sizeof(strDevTyp))) {
1246                 dwRet = MCIERR_BAD_INTEGER;
1247                 goto errCleanUp;
1248             }
1249         } else {
1250             LPSTR       ptr;
1251             if (lpParms->lpstrDeviceType == NULL) {
1252                 dwRet = MCIERR_NULL_PARAMETER_BLOCK;
1253                 goto errCleanUp;
1254             }
1255             strcpy(strDevTyp, lpParms->lpstrDeviceType);
1256             ptr = strchr(strDevTyp, '!');
1257             if (ptr) {
1258                 /* this behavior is not documented in windows. However, since, in
1259                  * some occasions, MCI_OPEN handling is translated by WinMM into
1260                  * a call to mciSendString("open <type>"); this code shall be correct
1261                  */
1262                 if (dwParam & MCI_OPEN_ELEMENT) {
1263                     ERR("Both MCI_OPEN_ELEMENT(%s) and %s are used\n",
1264                         lpParms->lpstrElementName, strDevTyp);
1265                     dwRet = MCIERR_UNRECOGNIZED_KEYWORD;
1266                     goto errCleanUp;
1267                 }
1268                 dwParam |= MCI_OPEN_ELEMENT;
1269                 *ptr++ = 0;
1270                 /* FIXME: not a good idea to write in user supplied buffer */
1271                 lpParms->lpstrElementName = ptr;
1272             }
1273
1274         }
1275         TRACE("devType='%s' !\n", strDevTyp);
1276     }
1277
1278     if (dwParam & MCI_OPEN_ELEMENT) {
1279         TRACE("lpstrElementName='%s'\n", lpParms->lpstrElementName);
1280
1281         if (dwParam & MCI_OPEN_ELEMENT_ID) {
1282             FIXME("Unsupported yet flag MCI_OPEN_ELEMENT_ID\n");
1283             dwRet = MCIERR_UNRECOGNIZED_KEYWORD;
1284             goto errCleanUp;
1285         }
1286
1287         if (!lpParms->lpstrElementName) {
1288             dwRet = MCIERR_NULL_PARAMETER_BLOCK;
1289             goto errCleanUp;
1290         }
1291
1292         /* type, if given as a parameter, supersedes file extension */
1293         if (!strDevTyp[0] &&
1294             MCI_GetDevTypeFromFileName(lpParms->lpstrElementName,
1295                                        strDevTyp, sizeof(strDevTyp))) {
1296             if (GetDriveTypeA(lpParms->lpstrElementName) != DRIVE_CDROM) {
1297                 dwRet = MCIERR_EXTENSION_NOT_FOUND;
1298                 goto errCleanUp;
1299             }
1300             /* FIXME: this will not work if several CDROM drives are installed on the machine */
1301             strcpy(strDevTyp, "CDAUDIO");
1302         }
1303     }
1304
1305     if (strDevTyp[0] == 0) {
1306         FIXME("Couldn't load driver\n");
1307         dwRet = MCIERR_INVALID_DEVICE_NAME;
1308         goto errCleanUp;
1309     }
1310
1311     if (dwParam & MCI_OPEN_ALIAS) {
1312         TRACE("Alias='%s' !\n", lpParms->lpstrAlias);
1313         if (!lpParms->lpstrAlias) {
1314             dwRet = MCIERR_NULL_PARAMETER_BLOCK;
1315             goto errCleanUp;
1316         }
1317     }
1318
1319     if ((dwRet = MCI_LoadMciDriver(strDevTyp, &wmd))) {
1320         goto errCleanUp;
1321     }
1322
1323     if ((dwRet = MCI_FinishOpen(wmd, lpParms, dwParam))) {
1324         TRACE("Failed to open driver (MCI_OPEN_DRIVER) [%08lx], closing\n", dwRet);
1325         /* FIXME: is dwRet the correct ret code ? */
1326         goto errCleanUp;
1327     }
1328
1329     /* only handled devices fall through */
1330     TRACE("wDevID=%04X wDeviceID=%d dwRet=%ld\n", wmd->wDeviceID, lpParms->wDeviceID, dwRet);
1331
1332     if (dwParam & MCI_NOTIFY)
1333         mciDriverNotify(lpParms->dwCallback, wmd->wDeviceID, MCI_NOTIFY_SUCCESSFUL);
1334
1335     return 0;
1336 errCleanUp:
1337     if (wmd) MCI_UnLoadMciDriver(wmd);
1338
1339     if (dwParam & MCI_NOTIFY)
1340         mciDriverNotify(lpParms->dwCallback, 0, MCI_NOTIFY_FAILURE);
1341     return dwRet;
1342 }
1343
1344 /**************************************************************************
1345  *                      MCI_Close                               [internal]
1346  */
1347 static  DWORD MCI_Close(UINT16 wDevID, DWORD dwParam, LPMCI_GENERIC_PARMS lpParms)
1348 {
1349     DWORD               dwRet;
1350     LPWINE_MCIDRIVER    wmd;
1351
1352     TRACE("(%04x, %08lX, %p)\n", wDevID, dwParam, lpParms);
1353
1354     if (wDevID == MCI_ALL_DEVICE_ID) {
1355         LPWINE_MCIDRIVER        next;
1356
1357         EnterCriticalSection(&WINMM_IData->cs);
1358         /* FIXME: shall I notify once after all is done, or for
1359          * each of the open drivers ? if the latest, which notif
1360          * to return when only one fails ?
1361          */
1362         for (wmd = WINMM_IData->lpMciDrvs; wmd; ) {
1363             next = wmd->lpNext;
1364             MCI_Close(wmd->wDeviceID, dwParam, lpParms);
1365             wmd = next;
1366         }
1367         LeaveCriticalSection(&WINMM_IData->cs);
1368         return 0;
1369     }
1370
1371     if (!(wmd = MCI_GetDriver(wDevID))) {
1372         return MCIERR_INVALID_DEVICE_ID;
1373     }
1374
1375     dwRet = MCI_SendCommandFrom32(wDevID, MCI_CLOSE_DRIVER, dwParam, (DWORD)lpParms);
1376
1377     MCI_UnLoadMciDriver(wmd);
1378
1379     if (dwParam & MCI_NOTIFY)
1380         mciDriverNotify(lpParms->dwCallback, wDevID,
1381                         (dwRet == 0) ? MCI_NOTIFY_SUCCESSFUL : MCI_NOTIFY_FAILURE);
1382
1383     return dwRet;
1384 }
1385
1386 /**************************************************************************
1387  *                      MCI_WriteString                         [internal]
1388  */
1389 DWORD   MCI_WriteString(LPSTR lpDstStr, DWORD dstSize, LPCSTR lpSrcStr)
1390 {
1391     DWORD       ret = 0;
1392
1393     if (lpSrcStr) {
1394         if (dstSize <= strlen(lpSrcStr)) {
1395             lstrcpynA(lpDstStr, lpSrcStr, dstSize - 1);
1396             ret = MCIERR_PARAM_OVERFLOW;
1397         } else {
1398             strcpy(lpDstStr, lpSrcStr);
1399         }
1400     } else {
1401         *lpDstStr = 0;
1402     }
1403     return ret;
1404 }
1405
1406 /**************************************************************************
1407  *                      MCI_Sysinfo                             [internal]
1408  */
1409 static  DWORD MCI_SysInfo(UINT uDevID, DWORD dwFlags, LPMCI_SYSINFO_PARMSA lpParms)
1410 {
1411     DWORD               ret = MCIERR_INVALID_DEVICE_ID;
1412     LPWINE_MCIDRIVER    wmd;
1413
1414     if (lpParms == NULL)                        return MCIERR_NULL_PARAMETER_BLOCK;
1415
1416     TRACE("(%08x, %08lX, %08lX[num=%ld, wDevTyp=%u])\n",
1417           uDevID, dwFlags, (DWORD)lpParms, lpParms->dwNumber, lpParms->wDeviceType);
1418
1419     switch (dwFlags & ~MCI_SYSINFO_OPEN) {
1420     case MCI_SYSINFO_QUANTITY:
1421         {
1422             DWORD       cnt = 0;
1423
1424             if (lpParms->wDeviceType < MCI_DEVTYPE_FIRST ||
1425                 lpParms->wDeviceType > MCI_DEVTYPE_LAST) {
1426                 if (dwFlags & MCI_SYSINFO_OPEN) {
1427                     TRACE("MCI_SYSINFO_QUANTITY: # of open MCI drivers\n");
1428                     EnterCriticalSection(&WINMM_IData->cs);
1429                     for (wmd = WINMM_IData->lpMciDrvs; wmd; wmd = wmd->lpNext) {
1430                         cnt++;
1431                     }
1432                     LeaveCriticalSection(&WINMM_IData->cs);
1433                 } else {
1434                     TRACE("MCI_SYSINFO_QUANTITY: # of installed MCI drivers\n");
1435                     cnt = MCI_InstalledCount;
1436                 }
1437             } else {
1438                 if (dwFlags & MCI_SYSINFO_OPEN) {
1439                     TRACE("MCI_SYSINFO_QUANTITY: # of open MCI drivers of type %u\n",
1440                           lpParms->wDeviceType);
1441                     EnterCriticalSection(&WINMM_IData->cs);
1442                     for (wmd = WINMM_IData->lpMciDrvs; wmd; wmd = wmd->lpNext) {
1443                         if (wmd->wType == lpParms->wDeviceType)
1444                             cnt++;
1445                     }
1446                     LeaveCriticalSection(&WINMM_IData->cs);
1447                 } else {
1448                     TRACE("MCI_SYSINFO_QUANTITY: # of installed MCI drivers of type %u\n",
1449                           lpParms->wDeviceType);
1450                     FIXME("Don't know how to get # of MCI devices of a given type\n");
1451                     cnt = 1;
1452                 }
1453             }
1454             *(DWORD*)lpParms->lpstrReturn = cnt;
1455         }
1456         TRACE("(%ld) => '%ld'\n", lpParms->dwNumber, *(DWORD*)lpParms->lpstrReturn);
1457         ret = MCI_INTEGER_RETURNED;
1458         break;
1459     case MCI_SYSINFO_INSTALLNAME:
1460         TRACE("MCI_SYSINFO_INSTALLNAME \n");
1461         if ((wmd = MCI_GetDriver(uDevID))) {
1462             ret = MCI_WriteString(lpParms->lpstrReturn, lpParms->dwRetSize,
1463                                   wmd->lpstrDeviceType);
1464         } else {
1465             *lpParms->lpstrReturn = 0;
1466             ret = MCIERR_INVALID_DEVICE_ID;
1467         }
1468         TRACE("(%ld) => '%s'\n", lpParms->dwNumber, lpParms->lpstrReturn);
1469         break;
1470     case MCI_SYSINFO_NAME:
1471         TRACE("MCI_SYSINFO_NAME\n");
1472         if (dwFlags & MCI_SYSINFO_OPEN) {
1473             FIXME("Don't handle MCI_SYSINFO_NAME|MCI_SYSINFO_OPEN (yet)\n");
1474             ret = MCIERR_UNRECOGNIZED_COMMAND;
1475         } else if (lpParms->dwNumber > MCI_InstalledCount) {
1476             ret = MCIERR_OUTOFRANGE;
1477         } else {
1478             DWORD       count = lpParms->dwNumber;
1479             LPSTR       ptr = MCI_lpInstallNames;
1480
1481             while (--count > 0) ptr += strlen(ptr) + 1;
1482             ret = MCI_WriteString(lpParms->lpstrReturn, lpParms->dwRetSize, ptr);
1483         }
1484         TRACE("(%ld) => '%s'\n", lpParms->dwNumber, lpParms->lpstrReturn);
1485         break;
1486     default:
1487         TRACE("Unsupported flag value=%08lx\n", dwFlags);
1488         ret = MCIERR_UNRECOGNIZED_COMMAND;
1489     }
1490     return ret;
1491 }
1492
1493 /**************************************************************************
1494  *                      MCI_Break                               [internal]
1495  */
1496 static  DWORD MCI_Break(UINT wDevID, DWORD dwFlags, LPMCI_BREAK_PARMS lpParms)
1497 {
1498     DWORD       dwRet = 0;
1499
1500     if (lpParms == NULL)        return MCIERR_NULL_PARAMETER_BLOCK;
1501
1502     if (dwFlags & MCI_NOTIFY)
1503         mciDriverNotify(lpParms->dwCallback, wDevID,
1504                         (dwRet == 0) ? MCI_NOTIFY_SUCCESSFUL : MCI_NOTIFY_FAILURE);
1505
1506     return dwRet;
1507 }
1508
1509 /**************************************************************************
1510  *                      MCI_SendCommand                         [internal]
1511  */
1512 DWORD   MCI_SendCommand(UINT wDevID, UINT16 wMsg, DWORD dwParam1,
1513                         DWORD dwParam2, BOOL bFrom32)
1514 {
1515     DWORD               dwRet = MCIERR_UNRECOGNIZED_COMMAND;
1516
1517     switch (wMsg) {
1518     case MCI_OPEN:
1519         if (bFrom32) {
1520             dwRet = MCI_Open(dwParam1, (LPMCI_OPEN_PARMSA)dwParam2);
1521         } else {
1522             switch (MCI_MapMsg16To32A(0, wMsg, &dwParam2)) {
1523             case WINMM_MAP_OK:
1524             case WINMM_MAP_OKMEM:
1525                 dwRet = MCI_Open(dwParam1, (LPMCI_OPEN_PARMSA)dwParam2);
1526                 MCI_UnMapMsg16To32A(0, wMsg, dwParam2);
1527                 break;
1528             default: break; /* so that gcc does not bark */
1529             }
1530         }
1531         break;
1532     case MCI_CLOSE:
1533         if (bFrom32) {
1534             dwRet = MCI_Close(wDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1535         } else {
1536             switch (MCI_MapMsg16To32A(0, wMsg, &dwParam2)) {
1537             case WINMM_MAP_OK:
1538             case WINMM_MAP_OKMEM:
1539                 dwRet = MCI_Close(wDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1540                 MCI_UnMapMsg16To32A(0, wMsg, dwParam2);
1541                 break;
1542             default: break; /* so that gcc does not bark */
1543             }
1544         }
1545         break;
1546     case MCI_SYSINFO:
1547         if (bFrom32) {
1548             dwRet = MCI_SysInfo(wDevID, dwParam1, (LPMCI_SYSINFO_PARMSA)dwParam2);
1549         } else {
1550             switch (MCI_MapMsg16To32A(0, wMsg, &dwParam2)) {
1551             case WINMM_MAP_OK:
1552             case WINMM_MAP_OKMEM:
1553                 dwRet = MCI_SysInfo(wDevID, dwParam1, (LPMCI_SYSINFO_PARMSA)dwParam2);
1554                 MCI_UnMapMsg16To32A(0, wMsg, dwParam2);
1555                 break;
1556             default: break; /* so that gcc doesnot  bark */
1557             }
1558         }
1559         break;
1560     case MCI_BREAK:
1561         if (bFrom32) {
1562             dwRet = MCI_Break(wDevID, dwParam1, (LPMCI_BREAK_PARMS)dwParam2);
1563         } else {
1564             switch (MCI_MapMsg16To32A(0, wMsg, &dwParam2)) {
1565             case WINMM_MAP_OK:
1566             case WINMM_MAP_OKMEM:
1567                 dwRet = MCI_Break(wDevID, dwParam1, (LPMCI_BREAK_PARMS)dwParam2);
1568                 MCI_UnMapMsg16To32A(0, wMsg, dwParam2);
1569                 break;
1570             default: break; /* so that gcc does not bark */
1571             }
1572         }
1573         break;
1574     case MCI_SOUND:
1575         /* FIXME: it seems that MCI_SOUND needs the same handling as MCI_BREAK
1576          * but I couldn't get any doc on this MCI message
1577          */
1578         break;
1579     default:
1580         if (wDevID == MCI_ALL_DEVICE_ID) {
1581             FIXME("unhandled MCI_ALL_DEVICE_ID\n");
1582             dwRet = MCIERR_CANNOT_USE_ALL;
1583         } else {
1584             dwRet = (bFrom32) ?
1585                 MCI_SendCommandFrom32(wDevID, wMsg, dwParam1, dwParam2) :
1586                 MCI_SendCommandFrom16(wDevID, wMsg, dwParam1, dwParam2);
1587         }
1588         break;
1589     }
1590     return dwRet;
1591 }
1592
1593 /**************************************************************************
1594  *                              MCI_CleanUp                     [internal]
1595  *
1596  * Some MCI commands need to be cleaned-up (when not called from
1597  * mciSendString), because MCI drivers return extra information for string
1598  * transformation. This function gets rid of them.
1599  */
1600 LRESULT         MCI_CleanUp(LRESULT dwRet, UINT wMsg, DWORD dwParam2, BOOL bIs32)
1601 {
1602     if (LOWORD(dwRet))
1603         return LOWORD(dwRet);
1604
1605     switch (wMsg) {
1606     case MCI_GETDEVCAPS:
1607         switch (dwRet & 0xFFFF0000ul) {
1608         case 0:
1609         case MCI_COLONIZED3_RETURN:
1610         case MCI_COLONIZED4_RETURN:
1611         case MCI_INTEGER_RETURNED:
1612             /* nothing to do */
1613             break;
1614         case MCI_RESOURCE_RETURNED:
1615         case MCI_RESOURCE_RETURNED|MCI_RESOURCE_DRIVER:
1616             {
1617                 LPMCI_GETDEVCAPS_PARMS  lmgp;
1618
1619                 lmgp = (LPMCI_GETDEVCAPS_PARMS)(bIs32 ? (void*)dwParam2 : MapSL(dwParam2));
1620                 TRACE("Changing %08lx to %08lx\n", lmgp->dwReturn, (DWORD)LOWORD(lmgp->dwReturn));
1621                 lmgp->dwReturn = LOWORD(lmgp->dwReturn);
1622             }
1623             break;
1624         default:
1625             FIXME("Unsupported value for hiword (%04x) returned by DriverProc(%s)\n",
1626                   HIWORD(dwRet), MCI_MessageToString(wMsg));
1627         }
1628         break;
1629     case MCI_STATUS:
1630         switch (dwRet & 0xFFFF0000ul) {
1631         case 0:
1632         case MCI_COLONIZED3_RETURN:
1633         case MCI_COLONIZED4_RETURN:
1634         case MCI_INTEGER_RETURNED:
1635             /* nothing to do */
1636             break;
1637         case MCI_RESOURCE_RETURNED:
1638         case MCI_RESOURCE_RETURNED|MCI_RESOURCE_DRIVER:
1639             {
1640                 LPMCI_STATUS_PARMS      lsp;
1641
1642                 lsp = (LPMCI_STATUS_PARMS)(bIs32 ? (void*)dwParam2 : MapSL(dwParam2));
1643                 TRACE("Changing %08lx to %08lx\n", lsp->dwReturn, (DWORD)LOWORD(lsp->dwReturn));
1644                 lsp->dwReturn = LOWORD(lsp->dwReturn);
1645             }
1646             break;
1647         default:
1648             FIXME("Unsupported value for hiword (%04x) returned by DriverProc(%s)\n",
1649                   HIWORD(dwRet), MCI_MessageToString(wMsg));
1650         }
1651         break;
1652     case MCI_SYSINFO:
1653         switch (dwRet & 0xFFFF0000ul) {
1654         case 0:
1655         case MCI_INTEGER_RETURNED:
1656             /* nothing to do */
1657             break;
1658         default:
1659             FIXME("Unsupported value for hiword (%04x)\n", HIWORD(dwRet));
1660         }
1661         break;
1662     default:
1663         if (HIWORD(dwRet)) {
1664             FIXME("Got non null hiword for dwRet=0x%08lx for command %s\n",
1665                   dwRet, MCI_MessageToString(wMsg));
1666         }
1667         break;
1668     }
1669     return LOWORD(dwRet);
1670 }
1671
1672 /**************************************************************************
1673  *                      MULTIMEDIA_MciInit                      [internal]
1674  *
1675  * Initializes the MCI internal variables.
1676  *
1677  */
1678 BOOL MULTIMEDIA_MciInit(void)
1679 {
1680     LPSTR       ptr1, ptr2;
1681     HKEY        hWineConf;
1682     HKEY        hkey;
1683     DWORD       err;
1684     DWORD       type;
1685     DWORD       count = 2048;
1686
1687     MCI_InstalledCount = 0;
1688     ptr1 = MCI_lpInstallNames = HeapAlloc(GetProcessHeap(), 0, count);
1689
1690     if (!MCI_lpInstallNames)
1691         return FALSE;
1692
1693     /* FIXME: should do also some registry diving here ? */
1694     if (!(err = RegOpenKeyA(HKEY_LOCAL_MACHINE, "Software\\Wine\\Wine\\Config", &hWineConf)) &&
1695         !(err = RegOpenKeyA(hWineConf, "options", &hkey))) {
1696         err = RegQueryValueExA(hkey, "mci", 0, &type, MCI_lpInstallNames, &count);
1697         RegCloseKey(hkey);
1698
1699     }
1700     if (!err) {
1701         TRACE("Wine => '%s' \n", ptr1);
1702         while ((ptr2 = strchr(ptr1, ':')) != 0) {
1703             *ptr2++ = 0;
1704             TRACE("---> '%s' \n", ptr1);
1705             MCI_InstalledCount++;
1706             ptr1 = ptr2;
1707         }
1708         MCI_InstalledCount++;
1709         TRACE("---> '%s' \n", ptr1);
1710         ptr1 += strlen(ptr1) + 1;
1711     } else {
1712         GetPrivateProfileStringA("mci", NULL, "", MCI_lpInstallNames, count, "SYSTEM.INI");
1713         while (strlen(ptr1) > 0) {
1714             TRACE("---> '%s' \n", ptr1);
1715             ptr1 += strlen(ptr1) + 1;
1716             MCI_InstalledCount++;
1717         }
1718     }
1719     RegCloseKey(hWineConf);
1720     return TRUE;
1721 }