mscoree/tests: Added some simple tests for GetCORVersion.
[wine] / dlls / mcicda / mcicda.c
1 /*
2  * MCI driver for audio CD (MCICDA)
3  *
4  * Copyright 1994    Martin Ayotte
5  * Copyright 1998-99 Eric Pouech
6  * Copyright 2000    Andreas Mohr
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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
21  */
22
23 #include "config.h"
24 #include <stdarg.h>
25 #include <stdio.h>
26 #include <string.h>
27
28 #define WIN32_NO_STATUS
29 #include "windef.h"
30 #include "winbase.h"
31 #include "wingdi.h"
32 #include "winuser.h"
33 #include "wownt32.h"
34 #include "mmddk.h"
35 #include "winioctl.h"
36 #include "ntddcdrm.h"
37 #include "winternl.h"
38 #include "wine/debug.h"
39 #include "wine/unicode.h"
40 #include "dsound.h"
41
42 WINE_DEFAULT_DEBUG_CHANNEL(mcicda);
43
44 #define CDFRAMES_PERSEC                 75
45 #define CDFRAMES_PERMIN                 (CDFRAMES_PERSEC * 60)
46 #define FRAME_OF_ADDR(a) ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
47 #define FRAME_OF_TOC(toc, idx)  FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)
48
49 /* Defined by red-book standard; do not change! */
50 #define RAW_SECTOR_SIZE  (2352)
51
52 /* Must be >= RAW_SECTOR_SIZE */
53 #define CDDA_FRAG_SIZE   (32768)
54 /* Must be >= 2 */
55 #define CDDA_FRAG_COUNT  (3)
56
57 typedef struct {
58     UINT                wDevID;
59     int                 nUseCount;          /* Incremented for each shared open */
60     BOOL                fShareable;         /* TRUE if first open was shareable */
61     MCIDEVICEID         wNotifyDeviceID;    /* MCI device ID with a pending notification */
62     HANDLE              hCallback;          /* Callback handle for pending notification */
63     DWORD               dwTimeFormat;
64     HANDLE              handle;
65
66     /* The following are used for digital playback only */
67     HANDLE hThread;
68     HANDLE stopEvent;
69     DWORD start, end;
70
71     IDirectSound *dsObj;
72     IDirectSoundBuffer *dsBuf;
73
74     CRITICAL_SECTION cs;
75 } WINE_MCICDAUDIO;
76
77 /*-----------------------------------------------------------------------*/
78
79 typedef HRESULT(WINAPI*LPDIRECTSOUNDCREATE)(LPCGUID,LPDIRECTSOUND*,LPUNKNOWN);
80 static LPDIRECTSOUNDCREATE pDirectSoundCreate;
81
82 static DWORD CALLBACK MCICDA_playLoop(void *ptr)
83 {
84     WINE_MCICDAUDIO *wmcda = (WINE_MCICDAUDIO*)ptr;
85     DWORD lastPos, curPos, endPos, br;
86     void *cdData;
87     DWORD lockLen, fragLen;
88     DSBCAPS caps;
89     RAW_READ_INFO rdInfo;
90     HRESULT hr = DS_OK;
91
92     memset(&caps, 0, sizeof(caps));
93     caps.dwSize = sizeof(caps);
94     hr = IDirectSoundBuffer_GetCaps(wmcda->dsBuf, &caps);
95
96     fragLen = caps.dwBufferBytes/CDDA_FRAG_COUNT;
97     curPos = lastPos = 0;
98     endPos = ~0u;
99     while (SUCCEEDED(hr) && endPos != lastPos &&
100            WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0) {
101         hr = IDirectSoundBuffer_GetCurrentPosition(wmcda->dsBuf, &curPos, NULL);
102         if ((curPos-lastPos+caps.dwBufferBytes)%caps.dwBufferBytes < fragLen) {
103             Sleep(1);
104             continue;
105         }
106
107         EnterCriticalSection(&wmcda->cs);
108         rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
109         rdInfo.SectorCount = min(fragLen/RAW_SECTOR_SIZE, wmcda->end-wmcda->start);
110         rdInfo.TrackMode = CDDA;
111
112         hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
113         if (hr == DSERR_BUFFERLOST) {
114             if(FAILED(IDirectSoundBuffer_Restore(wmcda->dsBuf)) ||
115                FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING))) {
116                 LeaveCriticalSection(&wmcda->cs);
117                 break;
118             }
119             hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
120         }
121
122         if (SUCCEEDED(hr)) {
123             if (rdInfo.SectorCount > 0) {
124                 if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_RAW_READ, &rdInfo, sizeof(rdInfo), cdData, lockLen, &br, NULL))
125                     WARN("CD read failed at sector %d: 0x%x\n", wmcda->start, GetLastError());
126             }
127             if (rdInfo.SectorCount*RAW_SECTOR_SIZE < lockLen) {
128                 if(endPos == ~0u) endPos = lastPos;
129                 memset((BYTE*)cdData + rdInfo.SectorCount*RAW_SECTOR_SIZE, 0,
130                        lockLen - rdInfo.SectorCount*RAW_SECTOR_SIZE);
131             }
132             hr = IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);
133         }
134
135         lastPos += fragLen;
136         lastPos %= caps.dwBufferBytes;
137         wmcda->start += rdInfo.SectorCount;
138
139         LeaveCriticalSection(&wmcda->cs);
140     }
141     IDirectSoundBuffer_Stop(wmcda->dsBuf);
142     SetEvent(wmcda->stopEvent);
143
144     /* A design bug in native: the independent CD player called by the
145      * MCI has no means to signal end of playing, therefore the MCI
146      * notification is left hanging.  MCI_NOTIFY_SUPERSEDED will be
147      * signaled by the next command that has MCI_NOTIFY set (or
148      * MCI_NOTIFY_ABORTED for MCI_PLAY). */
149
150     return 0;
151 }
152
153
154
155 /**************************************************************************
156  *                              MCICDA_drvOpen                  [internal]
157  */
158 static  DWORD   MCICDA_drvOpen(LPCWSTR str, LPMCI_OPEN_DRIVER_PARMSW modp)
159 {
160     static HMODULE dsHandle;
161     WINE_MCICDAUDIO*    wmcda;
162
163     if (!modp) return 0xFFFFFFFF;
164
165     wmcda = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sizeof(WINE_MCICDAUDIO));
166
167     if (!wmcda)
168         return 0;
169
170     if (!dsHandle) {
171         dsHandle = LoadLibraryA("dsound.dll");
172         if(dsHandle)
173             pDirectSoundCreate = (LPDIRECTSOUNDCREATE)GetProcAddress(dsHandle, "DirectSoundCreate");
174     }
175
176     wmcda->wDevID = modp->wDeviceID;
177     mciSetDriverData(wmcda->wDevID, (DWORD_PTR)wmcda);
178     modp->wCustomCommandTable = MCI_NO_COMMAND_TABLE;
179     modp->wType = MCI_DEVTYPE_CD_AUDIO;
180     InitializeCriticalSection(&wmcda->cs);
181     return modp->wDeviceID;
182 }
183
184 /**************************************************************************
185  *                              MCICDA_drvClose                 [internal]
186  */
187 static  DWORD   MCICDA_drvClose(DWORD dwDevID)
188 {
189     WINE_MCICDAUDIO*  wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(dwDevID);
190
191     if (wmcda) {
192         DeleteCriticalSection(&wmcda->cs);
193         HeapFree(GetProcessHeap(), 0, wmcda);
194         mciSetDriverData(dwDevID, 0);
195     }
196     return (dwDevID == 0xFFFFFFFF) ? 1 : 0;
197 }
198
199 /**************************************************************************
200  *                              MCICDA_GetOpenDrv               [internal]
201  */
202 static WINE_MCICDAUDIO*  MCICDA_GetOpenDrv(UINT wDevID)
203 {
204     WINE_MCICDAUDIO*    wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
205
206     if (wmcda == NULL || wmcda->nUseCount == 0) {
207         WARN("Invalid wDevID=%u\n", wDevID);
208         return 0;
209     }
210     return wmcda;
211 }
212
213 /**************************************************************************
214  *                              MCICDA_mciNotify                [internal]
215  *
216  * Notifications in MCI work like a 1-element queue.
217  * Each new notification request supersedes the previous one.
218  */
219 static void MCICDA_Notify(DWORD_PTR hWndCallBack, WINE_MCICDAUDIO* wmcda, UINT wStatus)
220 {
221     MCIDEVICEID wDevID = wmcda->wNotifyDeviceID;
222     HANDLE old = InterlockedExchangePointer(&wmcda->hCallback, NULL);
223     if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_SUPERSEDED);
224     mciDriverNotify(HWND_32(LOWORD(hWndCallBack)), wDevID, wStatus);
225 }
226
227 /**************************************************************************
228  *                              MCICDA_GetStatus                [internal]
229  */
230 static  DWORD    MCICDA_GetStatus(WINE_MCICDAUDIO* wmcda)
231 {
232     CDROM_SUB_Q_DATA_FORMAT     fmt;
233     SUB_Q_CHANNEL_DATA          data;
234     DWORD                       br;
235     DWORD                       mode = MCI_MODE_NOT_READY;
236
237     fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
238     if(wmcda->hThread != 0) {
239         DWORD status;
240         HRESULT hr;
241
242         hr = IDirectSoundBuffer_GetStatus(wmcda->dsBuf, &status);
243         if(SUCCEEDED(hr)) {
244             if(!(status&DSBSTATUS_PLAYING)) {
245                 if(WaitForSingleObject(wmcda->stopEvent, 0) == WAIT_OBJECT_0)
246                     mode = MCI_MODE_STOP;
247                 else
248                     mode = MCI_MODE_PAUSE;
249             }
250             else
251                 mode = MCI_MODE_PLAY;
252         }
253     }
254     else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
255                               &data, sizeof(data), &br, NULL)) {
256         if (GetLastError() == ERROR_NOT_READY) mode = MCI_MODE_OPEN;
257     } else {
258         switch (data.CurrentPosition.Header.AudioStatus)
259         {
260         case AUDIO_STATUS_IN_PROGRESS:          mode = MCI_MODE_PLAY;   break;
261         case AUDIO_STATUS_PAUSED:               mode = MCI_MODE_PAUSE;  break;
262         case AUDIO_STATUS_NO_STATUS:
263         case AUDIO_STATUS_PLAY_COMPLETE:        mode = MCI_MODE_STOP;   break;
264         case AUDIO_STATUS_PLAY_ERROR:
265         case AUDIO_STATUS_NOT_SUPPORTED:
266         default:
267             break;
268         }
269     }
270     return mode;
271 }
272
273 /**************************************************************************
274  *                              MCICDA_GetError                 [internal]
275  */
276 static  int     MCICDA_GetError(WINE_MCICDAUDIO* wmcda)
277 {
278     switch (GetLastError())
279     {
280     case ERROR_NOT_READY:     return MCIERR_DEVICE_NOT_READY;
281     case ERROR_NOT_SUPPORTED:
282     case ERROR_IO_DEVICE:     return MCIERR_HARDWARE;
283     default:
284         FIXME("Unknown mode %u\n", GetLastError());
285     }
286     return MCIERR_DRIVER_INTERNAL;
287 }
288
289 /**************************************************************************
290  *                      MCICDA_CalcFrame                        [internal]
291  */
292 static DWORD MCICDA_CalcFrame(WINE_MCICDAUDIO* wmcda, DWORD dwTime)
293 {
294     DWORD       dwFrame = 0;
295     UINT        wTrack;
296     CDROM_TOC   toc;
297     DWORD       br;
298     BYTE*       addr;
299
300     TRACE("(%p, %08X, %u);\n", wmcda, wmcda->dwTimeFormat, dwTime);
301
302     switch (wmcda->dwTimeFormat) {
303     case MCI_FORMAT_MILLISECONDS:
304         dwFrame = ((dwTime - 1) * CDFRAMES_PERSEC + 500) / 1000;
305         TRACE("MILLISECONDS %u\n", dwFrame);
306         break;
307     case MCI_FORMAT_MSF:
308         TRACE("MSF %02u:%02u:%02u\n",
309               MCI_MSF_MINUTE(dwTime), MCI_MSF_SECOND(dwTime), MCI_MSF_FRAME(dwTime));
310         dwFrame += CDFRAMES_PERMIN * MCI_MSF_MINUTE(dwTime);
311         dwFrame += CDFRAMES_PERSEC * MCI_MSF_SECOND(dwTime);
312         dwFrame += MCI_MSF_FRAME(dwTime);
313         break;
314     case MCI_FORMAT_TMSF:
315     default: /* unknown format ! force TMSF ! ... */
316         wTrack = MCI_TMSF_TRACK(dwTime);
317         if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
318                              &toc, sizeof(toc), &br, NULL))
319             return 0;
320         if (wTrack < toc.FirstTrack || wTrack > toc.LastTrack)
321             return 0;
322         TRACE("MSF %02u-%02u:%02u:%02u\n",
323               MCI_TMSF_TRACK(dwTime), MCI_TMSF_MINUTE(dwTime),
324               MCI_TMSF_SECOND(dwTime), MCI_TMSF_FRAME(dwTime));
325         addr = toc.TrackData[wTrack - toc.FirstTrack].Address;
326         TRACE("TMSF trackpos[%u]=%d:%d:%d\n",
327               wTrack, addr[1], addr[2], addr[3]);
328         dwFrame = CDFRAMES_PERMIN * (addr[1] + MCI_TMSF_MINUTE(dwTime)) +
329             CDFRAMES_PERSEC * (addr[2] + MCI_TMSF_SECOND(dwTime)) +
330             addr[3] + MCI_TMSF_FRAME(dwTime);
331         break;
332     }
333     return dwFrame;
334 }
335
336 /**************************************************************************
337  *                      MCICDA_CalcTime                         [internal]
338  */
339 static DWORD MCICDA_CalcTime(WINE_MCICDAUDIO* wmcda, DWORD tf, DWORD dwFrame, LPDWORD lpRet)
340 {
341     DWORD       dwTime = 0;
342     UINT        wTrack;
343     UINT        wMinutes;
344     UINT        wSeconds;
345     UINT        wFrames;
346     CDROM_TOC   toc;
347     DWORD       br;
348
349     TRACE("(%p, %08X, %u);\n", wmcda, tf, dwFrame);
350
351     switch (tf) {
352     case MCI_FORMAT_MILLISECONDS:
353         dwTime = (dwFrame * 1000) / CDFRAMES_PERSEC + 1;
354         TRACE("MILLISECONDS %u\n", dwTime);
355         *lpRet = 0;
356         break;
357     case MCI_FORMAT_MSF:
358         wMinutes = dwFrame / CDFRAMES_PERMIN;
359         wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
360         wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
361         dwTime = MCI_MAKE_MSF(wMinutes, wSeconds, wFrames);
362         TRACE("MSF %02u:%02u:%02u -> dwTime=%u\n",
363               wMinutes, wSeconds, wFrames, dwTime);
364         *lpRet = MCI_COLONIZED3_RETURN;
365         break;
366     case MCI_FORMAT_TMSF:
367     default:    /* unknown format ! force TMSF ! ... */
368         if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
369                              &toc, sizeof(toc), &br, NULL))
370             return 0;
371         if (dwFrame < FRAME_OF_TOC(toc, toc.FirstTrack) ||
372             dwFrame > FRAME_OF_TOC(toc, toc.LastTrack + 1)) {
373             ERR("Out of range value %u [%u,%u]\n",
374                 dwFrame, FRAME_OF_TOC(toc, toc.FirstTrack),
375                 FRAME_OF_TOC(toc, toc.LastTrack + 1));
376             *lpRet = 0;
377             return 0;
378         }
379         for (wTrack = toc.FirstTrack; wTrack <= toc.LastTrack; wTrack++) {
380             if (FRAME_OF_TOC(toc, wTrack) > dwFrame)
381                 break;
382         }
383         wTrack--;
384         dwFrame -= FRAME_OF_TOC(toc, wTrack);
385         wMinutes = dwFrame / CDFRAMES_PERMIN;
386         wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
387         wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
388         dwTime = MCI_MAKE_TMSF(wTrack, wMinutes, wSeconds, wFrames);
389         TRACE("%02u-%02u:%02u:%02u\n", wTrack, wMinutes, wSeconds, wFrames);
390         *lpRet = MCI_COLONIZED4_RETURN;
391         break;
392     }
393     return dwTime;
394 }
395
396 static DWORD MCICDA_Stop(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms);
397
398 /**************************************************************************
399  *                              MCICDA_Open                     [internal]
400  */
401 static DWORD MCICDA_Open(UINT wDevID, DWORD dwFlags, LPMCI_OPEN_PARMSW lpOpenParms)
402 {
403     DWORD               dwDeviceID;
404     DWORD               ret = MCIERR_HARDWARE;
405     WINE_MCICDAUDIO*    wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
406     WCHAR               root[7], drive = 0;
407     unsigned int        count;
408
409     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpOpenParms);
410
411     if (lpOpenParms == NULL)            return MCIERR_NULL_PARAMETER_BLOCK;
412     if (wmcda == NULL)                  return MCIERR_INVALID_DEVICE_ID;
413
414     dwDeviceID = lpOpenParms->wDeviceID;
415
416     if (wmcda->nUseCount > 0) {
417         /* The driver is already open on this channel */
418         /* If the driver was opened shareable before and this open specifies */
419         /* shareable then increment the use count */
420         if (wmcda->fShareable && (dwFlags & MCI_OPEN_SHAREABLE))
421             ++wmcda->nUseCount;
422         else
423             return MCIERR_MUST_USE_SHAREABLE;
424     } else {
425         wmcda->nUseCount = 1;
426         wmcda->fShareable = dwFlags & MCI_OPEN_SHAREABLE;
427     }
428     if (dwFlags & MCI_OPEN_ELEMENT) {
429         if (dwFlags & MCI_OPEN_ELEMENT_ID) {
430             WARN("MCI_OPEN_ELEMENT_ID %p! Abort\n", lpOpenParms->lpstrElementName);
431             ret = MCIERR_NO_ELEMENT_ALLOWED;
432             goto the_error;
433         }
434         TRACE("MCI_OPEN_ELEMENT element name: %s\n", debugstr_w(lpOpenParms->lpstrElementName));
435         if (!isalpha(lpOpenParms->lpstrElementName[0]) || lpOpenParms->lpstrElementName[1] != ':' ||
436             (lpOpenParms->lpstrElementName[2] && lpOpenParms->lpstrElementName[2] != '\\'))
437         {
438             WARN("MCI_OPEN_ELEMENT unsupported format: %s\n", 
439                  debugstr_w(lpOpenParms->lpstrElementName));
440             ret = MCIERR_NO_ELEMENT_ALLOWED;
441             goto the_error;
442         }
443         drive = toupper(lpOpenParms->lpstrElementName[0]);
444         root[0] = drive; root[1] = ':'; root[2] = '\\'; root[3] = '\0';
445         if (GetDriveTypeW(root) != DRIVE_CDROM)
446         {
447             ret = MCIERR_INVALID_DEVICE_NAME;
448             goto the_error;
449         }
450     }
451     else
452     {
453         /* drive letter isn't passed... get the dwDeviceID'th cdrom in the system */
454         root[0] = 'A'; root[1] = ':'; root[2] = '\\'; root[3] = '\0';
455         for (count = 0; root[0] <= 'Z'; root[0]++)
456         {
457             if (GetDriveTypeW(root) == DRIVE_CDROM && ++count >= dwDeviceID)
458             {
459                 drive = root[0];
460                 break;
461             }
462         }
463         if (!drive)
464         {
465             ret = MCIERR_INVALID_DEVICE_ID;
466             goto the_error;
467         }
468     }
469
470     wmcda->wNotifyDeviceID = dwDeviceID;
471     wmcda->dwTimeFormat = MCI_FORMAT_MSF;
472
473     /* now, open the handle */
474     root[0] = root[1] = '\\'; root[2] = '.'; root[3] = '\\'; root[4] = drive; root[5] = ':'; root[6] = '\0';
475     wmcda->handle = CreateFileW(root, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, 0);
476     if (wmcda->handle == INVALID_HANDLE_VALUE)
477         goto the_error;
478
479     if (dwFlags & MCI_NOTIFY) {
480         mciDriverNotify(HWND_32(LOWORD(lpOpenParms->dwCallback)),
481                         dwDeviceID, MCI_NOTIFY_SUCCESSFUL);
482     }
483     return 0;
484
485  the_error:
486     --wmcda->nUseCount;
487     return ret;
488 }
489
490 /**************************************************************************
491  *                              MCICDA_Close                    [internal]
492  */
493 static DWORD MCICDA_Close(UINT wDevID, DWORD dwParam, LPMCI_GENERIC_PARMS lpParms)
494 {
495     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
496
497     TRACE("(%04X, %08X, %p);\n", wDevID, dwParam, lpParms);
498
499     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
500
501     MCICDA_Stop(wDevID, MCI_WAIT, NULL);
502
503     if (--wmcda->nUseCount == 0) {
504         CloseHandle(wmcda->handle);
505     }
506     if ((dwParam & MCI_NOTIFY) && lpParms)
507         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
508     return 0;
509 }
510
511 /**************************************************************************
512  *                              MCICDA_GetDevCaps               [internal]
513  */
514 static DWORD MCICDA_GetDevCaps(UINT wDevID, DWORD dwFlags,
515                                    LPMCI_GETDEVCAPS_PARMS lpParms)
516 {
517     WINE_MCICDAUDIO*    wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
518     DWORD       ret = 0;
519
520     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
521
522     if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
523     if (wmcda == NULL)                  return MCIERR_INVALID_DEVICE_ID;
524
525     if (dwFlags & MCI_GETDEVCAPS_ITEM) {
526         TRACE("MCI_GETDEVCAPS_ITEM dwItem=%08X;\n", lpParms->dwItem);
527
528         switch (lpParms->dwItem) {
529         case MCI_GETDEVCAPS_CAN_RECORD:
530             lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
531             ret = MCI_RESOURCE_RETURNED;
532             break;
533         case MCI_GETDEVCAPS_HAS_AUDIO:
534             lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
535             ret = MCI_RESOURCE_RETURNED;
536             break;
537         case MCI_GETDEVCAPS_HAS_VIDEO:
538             lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
539             ret = MCI_RESOURCE_RETURNED;
540             break;
541         case MCI_GETDEVCAPS_DEVICE_TYPE:
542             lpParms->dwReturn = MAKEMCIRESOURCE(MCI_DEVTYPE_CD_AUDIO, MCI_DEVTYPE_CD_AUDIO);
543             ret = MCI_RESOURCE_RETURNED;
544             break;
545         case MCI_GETDEVCAPS_USES_FILES:
546             lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
547             ret = MCI_RESOURCE_RETURNED;
548             break;
549         case MCI_GETDEVCAPS_COMPOUND_DEVICE:
550             lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
551             ret = MCI_RESOURCE_RETURNED;
552             break;
553         case MCI_GETDEVCAPS_CAN_EJECT:
554             lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
555             ret = MCI_RESOURCE_RETURNED;
556             break;
557         case MCI_GETDEVCAPS_CAN_PLAY:
558             lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
559             ret = MCI_RESOURCE_RETURNED;
560             break;
561         case MCI_GETDEVCAPS_CAN_SAVE:
562             lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
563             ret = MCI_RESOURCE_RETURNED;
564             break;
565         default:
566             WARN("Unsupported %x devCaps item\n", lpParms->dwItem);
567             return MCIERR_UNSUPPORTED_FUNCTION;
568         }
569     } else {
570         TRACE("No GetDevCaps-Item !\n");
571         return MCIERR_MISSING_PARAMETER;
572     }
573     TRACE("lpParms->dwReturn=%08X;\n", lpParms->dwReturn);
574     if (dwFlags & MCI_NOTIFY) {
575         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
576     }
577     return ret;
578 }
579
580 static DWORD CDROM_Audio_GetSerial(CDROM_TOC* toc)
581 {
582     DWORD serial = 0;
583     int i;
584     WORD wMagic;
585     DWORD dwStart, dwEnd;
586
587     /*
588      * wMagic collects the wFrames from track 1
589      * dwStart, dwEnd collect the beginning and end of the disc respectively, in
590      * frames.
591      * There it is collected for correcting the serial when there are less than
592      * 3 tracks.
593      */
594     wMagic = toc->TrackData[0].Address[3];
595     dwStart = FRAME_OF_TOC(*toc, toc->FirstTrack);
596
597     for (i = 0; i <= toc->LastTrack - toc->FirstTrack; i++) {
598         serial += (toc->TrackData[i].Address[1] << 16) |
599             (toc->TrackData[i].Address[2] << 8) | toc->TrackData[i].Address[3];
600     }
601     dwEnd = FRAME_OF_TOC(*toc, toc->LastTrack + 1);
602
603     if (toc->LastTrack - toc->FirstTrack + 1 < 3)
604         serial += wMagic + (dwEnd - dwStart);
605
606     return serial;
607 }
608
609
610 /**************************************************************************
611  *                              MCICDA_Info                     [internal]
612  */
613 static DWORD MCICDA_Info(UINT wDevID, DWORD dwFlags, LPMCI_INFO_PARMSW lpParms)
614 {
615     LPCWSTR             str = NULL;
616     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
617     DWORD               ret = 0;
618     WCHAR               buffer[16];
619
620     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
621
622     if (lpParms == NULL || lpParms->lpstrReturn == NULL)
623         return MCIERR_NULL_PARAMETER_BLOCK;
624     if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
625
626     TRACE("buf=%p, len=%u\n", lpParms->lpstrReturn, lpParms->dwRetSize);
627
628     if (dwFlags & MCI_INFO_PRODUCT) {
629         static const WCHAR wszAudioCd[] = {'W','i','n','e','\'','s',' ','a','u','d','i','o',' ','C','D',0};
630         str = wszAudioCd;
631     } else if (dwFlags & MCI_INFO_MEDIA_UPC) {
632         ret = MCIERR_NO_IDENTITY;
633     } else if (dwFlags & MCI_INFO_MEDIA_IDENTITY) {
634         DWORD       res = 0;
635         CDROM_TOC   toc;
636         DWORD       br;
637         static const WCHAR wszLu[] = {'%','l','u',0};
638
639         if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
640                              &toc, sizeof(toc), &br, NULL)) {
641             return MCICDA_GetError(wmcda);
642         }
643
644         res = CDROM_Audio_GetSerial(&toc);
645         sprintfW(buffer, wszLu, res);
646         str = buffer;
647     } else {
648         WARN("Don't know this info command (%u)\n", dwFlags);
649         ret = MCIERR_MISSING_PARAMETER;
650     }
651     if (str) {
652         if (lpParms->dwRetSize <= strlenW(str)) {
653             lstrcpynW(lpParms->lpstrReturn, str, lpParms->dwRetSize - 1);
654             ret = MCIERR_PARAM_OVERFLOW;
655         } else {
656             strcpyW(lpParms->lpstrReturn, str);
657         }
658     } else {
659         *lpParms->lpstrReturn = 0;
660     }
661     TRACE("=> %s (%d)\n", debugstr_w(lpParms->lpstrReturn), ret);
662
663     if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
664         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
665     return ret;
666 }
667
668 /**************************************************************************
669  *                              MCICDA_Status                   [internal]
670  */
671 static DWORD MCICDA_Status(UINT wDevID, DWORD dwFlags, LPMCI_STATUS_PARMS lpParms)
672 {
673     WINE_MCICDAUDIO*            wmcda = MCICDA_GetOpenDrv(wDevID);
674     DWORD                       ret = 0;
675     CDROM_SUB_Q_DATA_FORMAT     fmt;
676     SUB_Q_CHANNEL_DATA          data;
677     CDROM_TOC                   toc;
678     DWORD                       br;
679
680     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
681
682     if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
683     if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
684
685     if (dwFlags & MCI_STATUS_ITEM) {
686         TRACE("dwItem = %x\n", lpParms->dwItem);
687         switch (lpParms->dwItem) {
688         case MCI_STATUS_CURRENT_TRACK:
689             fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
690             if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
691                                  &data, sizeof(data), &br, NULL))
692             {
693                 return MCICDA_GetError(wmcda);
694                 /* alt. data.CurrentPosition.TrackNumber = 1; -- what native yields */
695             }
696             lpParms->dwReturn = data.CurrentPosition.TrackNumber;
697             TRACE("CURRENT_TRACK=%lu\n", lpParms->dwReturn);
698             break;
699         case MCI_STATUS_LENGTH:
700             if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
701                                  &toc, sizeof(toc), &br, NULL)) {
702                 WARN("error reading TOC !\n");
703                 return MCICDA_GetError(wmcda);
704             }
705             if (dwFlags & MCI_TRACK) {
706                 TRACE("MCI_TRACK #%u LENGTH=??? !\n", lpParms->dwTrack);
707                 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
708                     return MCIERR_OUTOFRANGE;
709                 lpParms->dwReturn = FRAME_OF_TOC(toc, lpParms->dwTrack + 1) -
710                     FRAME_OF_TOC(toc, lpParms->dwTrack);
711                 /* Windows returns one frame less than the total track length for the
712                    last track on the CD.  See CDDB HOWTO.  Verified on Win95OSR2. */
713                 if (lpParms->dwTrack == toc.LastTrack)
714                     lpParms->dwReturn--;
715             } else {
716                 /* Sum of the lengths of all of the tracks.  Inherits the
717                    'off by one frame' behavior from the length of the last track.
718                    See above comment. */
719                 lpParms->dwReturn = FRAME_OF_TOC(toc, toc.LastTrack + 1) -
720                     FRAME_OF_TOC(toc, toc.FirstTrack) - 1;
721             }
722             lpParms->dwReturn = MCICDA_CalcTime(wmcda,
723                                                  (wmcda->dwTimeFormat == MCI_FORMAT_TMSF)
724                                                     ? MCI_FORMAT_MSF : wmcda->dwTimeFormat,
725                                                  lpParms->dwReturn,
726                                                  &ret);
727             TRACE("LENGTH=%lu\n", lpParms->dwReturn);
728             break;
729         case MCI_STATUS_MODE:
730             lpParms->dwReturn = MCICDA_GetStatus(wmcda);
731             TRACE("MCI_STATUS_MODE=%08lX\n", lpParms->dwReturn);
732             lpParms->dwReturn = MAKEMCIRESOURCE(lpParms->dwReturn, lpParms->dwReturn);
733             ret = MCI_RESOURCE_RETURNED;
734             break;
735         case MCI_STATUS_MEDIA_PRESENT:
736             lpParms->dwReturn = (MCICDA_GetStatus(wmcda) == MCI_MODE_OPEN) ?
737                 MAKEMCIRESOURCE(FALSE, MCI_FALSE) : MAKEMCIRESOURCE(TRUE, MCI_TRUE);
738             TRACE("MCI_STATUS_MEDIA_PRESENT =%c!\n", LOWORD(lpParms->dwReturn) ? 'Y' : 'N');
739             ret = MCI_RESOURCE_RETURNED;
740             break;
741         case MCI_STATUS_NUMBER_OF_TRACKS:
742             if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
743                                  &toc, sizeof(toc), &br, NULL)) {
744                 WARN("error reading TOC !\n");
745                 return MCICDA_GetError(wmcda);
746             }
747             lpParms->dwReturn = toc.LastTrack - toc.FirstTrack + 1;
748             TRACE("MCI_STATUS_NUMBER_OF_TRACKS = %lu\n", lpParms->dwReturn);
749             if (lpParms->dwReturn == (WORD)-1)
750                 return MCICDA_GetError(wmcda);
751             break;
752         case MCI_STATUS_POSITION:
753             switch (dwFlags & (MCI_STATUS_START | MCI_TRACK)) {
754             case MCI_STATUS_START:
755                 if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
756                                      &toc, sizeof(toc), &br, NULL)) {
757                     WARN("error reading TOC !\n");
758                     return MCICDA_GetError(wmcda);
759                 }
760                 lpParms->dwReturn = FRAME_OF_TOC(toc, toc.FirstTrack);
761                 TRACE("get MCI_STATUS_START !\n");
762                 break;
763             case MCI_TRACK:
764                 if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
765                                      &toc, sizeof(toc), &br, NULL)) {
766                     WARN("error reading TOC !\n");
767                     return MCICDA_GetError(wmcda);
768                 }
769                 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
770                     return MCIERR_OUTOFRANGE;
771                 lpParms->dwReturn = FRAME_OF_TOC(toc, lpParms->dwTrack);
772                 TRACE("get MCI_TRACK #%u !\n", lpParms->dwTrack);
773                 break;
774             case 0:
775                 fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
776                 if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
777                                      &data, sizeof(data), &br, NULL)) {
778                     return MCICDA_GetError(wmcda);
779                 }
780                 lpParms->dwReturn = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
781                 break;
782             default:
783                 return MCIERR_FLAGS_NOT_COMPATIBLE;
784             }
785             lpParms->dwReturn = MCICDA_CalcTime(wmcda, wmcda->dwTimeFormat, lpParms->dwReturn, &ret);
786             TRACE("MCI_STATUS_POSITION=%08lX\n", lpParms->dwReturn);
787             break;
788         case MCI_STATUS_READY:
789             TRACE("MCI_STATUS_READY !\n");
790             switch (MCICDA_GetStatus(wmcda))
791             {
792             case MCI_MODE_NOT_READY:
793             case MCI_MODE_OPEN:
794                 lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
795                 break;
796             default:
797                 lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
798                 break;
799             }
800             TRACE("MCI_STATUS_READY=%u!\n", LOWORD(lpParms->dwReturn));
801             ret = MCI_RESOURCE_RETURNED;
802             break;
803         case MCI_STATUS_TIME_FORMAT:
804             lpParms->dwReturn = MAKEMCIRESOURCE(wmcda->dwTimeFormat, MCI_FORMAT_RETURN_BASE + wmcda->dwTimeFormat);
805             TRACE("MCI_STATUS_TIME_FORMAT=%08x!\n", LOWORD(lpParms->dwReturn));
806             ret = MCI_RESOURCE_RETURNED;
807             break;
808         case 4001: /* FIXME: for bogus FullCD */
809         case MCI_CDA_STATUS_TYPE_TRACK:
810             if (!(dwFlags & MCI_TRACK))
811                 ret = MCIERR_MISSING_PARAMETER;
812             else {
813                 if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
814                                      &toc, sizeof(toc), &br, NULL)) {
815                     WARN("error reading TOC !\n");
816                     return MCICDA_GetError(wmcda);
817                 }
818                 if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
819                     ret = MCIERR_OUTOFRANGE;
820                 else
821                     lpParms->dwReturn = (toc.TrackData[lpParms->dwTrack - toc.FirstTrack].Control & 0x04) ?
822                                          MCI_CDA_TRACK_OTHER : MCI_CDA_TRACK_AUDIO;
823                     /* FIXME: MAKEMCIRESOURCE "audio" | "other", localised */
824             }
825             TRACE("MCI_CDA_STATUS_TYPE_TRACK[%d]=%ld\n", lpParms->dwTrack, lpParms->dwReturn);
826             break;
827         default:
828             FIXME("unknown command %08X !\n", lpParms->dwItem);
829             return MCIERR_UNSUPPORTED_FUNCTION;
830         }
831     } else return MCIERR_MISSING_PARAMETER;
832     if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
833         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
834     return ret;
835 }
836
837 /**************************************************************************
838  *                              MCICDA_SkipDataTracks           [internal]
839  */
840 static DWORD MCICDA_SkipDataTracks(WINE_MCICDAUDIO* wmcda,DWORD *frame)
841 {
842   int i;
843   DWORD br;
844   CDROM_TOC toc;
845   if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
846                       &toc, sizeof(toc), &br, NULL)) {
847     WARN("error reading TOC !\n");
848     return MCICDA_GetError(wmcda);
849   }
850   if (*frame < FRAME_OF_TOC(toc,toc.FirstTrack) ||
851       *frame >= FRAME_OF_TOC(toc,toc.LastTrack+1)) /* lead-out */
852     return MCIERR_OUTOFRANGE;
853   for(i=toc.LastTrack+1;i>toc.FirstTrack;i--)
854     if ( FRAME_OF_TOC(toc, i) <= *frame ) break;
855   /* i points to last track whose start address is not greater than frame.
856    * Now skip non-audio tracks */
857   for(;i<=toc.LastTrack;i++)
858     if ( ! (toc.TrackData[i-toc.FirstTrack].Control & 4) )
859       break;
860   /* The frame will be an address in the next audio track or
861    * address of lead-out. */
862   if ( FRAME_OF_TOC(toc, i) > *frame )
863     *frame = FRAME_OF_TOC(toc, i);
864   /* Lead-out is an invalid seek position (on Linux as well). */
865   if (*frame == FRAME_OF_TOC(toc,toc.LastTrack+1))
866      (*frame)--;
867   return 0;
868 }
869
870 /**************************************************************************
871  *                              MCICDA_Play                     [internal]
872  */
873 static DWORD MCICDA_Play(UINT wDevID, DWORD dwFlags, LPMCI_PLAY_PARMS lpParms)
874 {
875     WINE_MCICDAUDIO*            wmcda = MCICDA_GetOpenDrv(wDevID);
876     DWORD                       ret = 0, start, end;
877     HANDLE                      oldcb;
878     DWORD                       br;
879     CDROM_PLAY_AUDIO_MSF        play;
880     CDROM_SUB_Q_DATA_FORMAT     fmt;
881     SUB_Q_CHANNEL_DATA          data;
882     CDROM_TOC                   toc;
883
884     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
885
886     if (lpParms == NULL)
887         return MCIERR_NULL_PARAMETER_BLOCK;
888
889     if (wmcda == NULL)
890         return MCIERR_INVALID_DEVICE_ID;
891
892     if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
893                          &toc, sizeof(toc), &br, NULL)) {
894         WARN("error reading TOC !\n");
895         return MCICDA_GetError(wmcda);
896     }
897
898     if (dwFlags & MCI_FROM) {
899         start = MCICDA_CalcFrame(wmcda, lpParms->dwFrom);
900         if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
901           return ret;
902         TRACE("MCI_FROM=%08X -> %u\n", lpParms->dwFrom, start);
903     } else {
904         fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
905         if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
906                              &data, sizeof(data), &br, NULL)) {
907             return MCICDA_GetError(wmcda);
908         }
909         start = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
910         if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
911           return ret;
912     }
913     if (dwFlags & MCI_TO) {
914         end = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
915         if ( (ret=MCICDA_SkipDataTracks(wmcda, &end)) )
916           return ret;
917         TRACE("MCI_TO=%08X -> %u\n", lpParms->dwTo, end);
918     } else {
919         end = FRAME_OF_TOC(toc, toc.LastTrack + 1) - 1;
920     }
921     if (end < start) return MCIERR_OUTOFRANGE;
922     TRACE("Playing from %u to %u\n", start, end);
923
924     oldcb = InterlockedExchangePointer(&wmcda->hCallback,
925         (dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
926     if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
927
928     if (start == end || start == FRAME_OF_TOC(toc,toc.LastTrack+1)-1) {
929         if (dwFlags & MCI_NOTIFY) {
930             oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
931             if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_SUCCESSFUL);
932         }
933         return MMSYSERR_NOERROR;
934     }
935
936     if (wmcda->hThread != 0) {
937         SetEvent(wmcda->stopEvent);
938         WaitForSingleObject(wmcda->hThread, INFINITE);
939
940         CloseHandle(wmcda->hThread);
941         wmcda->hThread = 0;
942         CloseHandle(wmcda->stopEvent);
943         wmcda->stopEvent = 0;
944
945         IDirectSoundBuffer_Stop(wmcda->dsBuf);
946         IDirectSoundBuffer_Release(wmcda->dsBuf);
947         wmcda->dsBuf = NULL;
948         IDirectSound_Release(wmcda->dsObj);
949         wmcda->dsObj = NULL;
950     }
951
952     if (pDirectSoundCreate) {
953         WAVEFORMATEX format;
954         DSBUFFERDESC desc;
955         DWORD lockLen;
956         void *cdData;
957         HRESULT hr;
958
959         hr = pDirectSoundCreate(NULL, &wmcda->dsObj, NULL);
960         if (SUCCEEDED(hr)) {
961             IDirectSound_SetCooperativeLevel(wmcda->dsObj, GetDesktopWindow(), DSSCL_PRIORITY);
962
963             /* The "raw" frame is relative to the start of the first track */
964             wmcda->start = start - FRAME_OF_TOC(toc, toc.FirstTrack);
965             wmcda->end = end - FRAME_OF_TOC(toc, toc.FirstTrack);
966
967             memset(&format, 0, sizeof(format));
968             format.wFormatTag = WAVE_FORMAT_PCM;
969             format.nChannels = 2;
970             format.nSamplesPerSec = 44100;
971             format.wBitsPerSample = 16;
972             format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
973             format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
974             format.cbSize = 0;
975
976             memset(&desc, 0, sizeof(desc));
977             desc.dwSize = sizeof(desc);
978             desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
979             desc.dwBufferBytes = (CDDA_FRAG_SIZE - (CDDA_FRAG_SIZE%RAW_SECTOR_SIZE)) * CDDA_FRAG_COUNT;
980             desc.lpwfxFormat = &format;
981
982             hr = IDirectSound_CreateSoundBuffer(wmcda->dsObj, &desc, &wmcda->dsBuf, NULL);
983         }
984         if (SUCCEEDED(hr)) {
985             hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, 0, 0, &cdData, &lockLen,
986                                          NULL, NULL, DSBLOCK_ENTIREBUFFER);
987         }
988         if (SUCCEEDED(hr)) {
989             RAW_READ_INFO rdInfo;
990             int readok;
991
992             rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
993             rdInfo.SectorCount = min(desc.dwBufferBytes/RAW_SECTOR_SIZE,
994                                      wmcda->end-wmcda->start);
995             rdInfo.TrackMode = CDDA;
996
997             readok = DeviceIoControl(wmcda->handle, IOCTL_CDROM_RAW_READ,
998                                      &rdInfo, sizeof(rdInfo), cdData, lockLen,
999                                      &br, NULL);
1000             IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);
1001
1002             if (readok) {
1003                 wmcda->start += rdInfo.SectorCount;
1004                 wmcda->stopEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
1005             }
1006             if (wmcda->stopEvent != 0)
1007                 wmcda->hThread = CreateThread(NULL, 0, MCICDA_playLoop, wmcda, 0, &br);
1008             if (wmcda->hThread != 0) {
1009                 hr = IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING);
1010                 if (SUCCEEDED(hr)) {
1011                     /* FIXME: implement MCI_WAIT and send notification only in that case */
1012                     if (0) {
1013                         oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1014                         if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID,
1015                             FAILED(hr) ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
1016                     }
1017                     return ret;
1018                 }
1019
1020                 SetEvent(wmcda->stopEvent);
1021                 WaitForSingleObject(wmcda->hThread, INFINITE);
1022                 CloseHandle(wmcda->hThread);
1023                 wmcda->hThread = 0;
1024             }
1025         }
1026
1027         if (wmcda->stopEvent != 0) {
1028             CloseHandle(wmcda->stopEvent);
1029             wmcda->stopEvent = 0;
1030         }
1031         if (wmcda->dsBuf) {
1032             IDirectSoundBuffer_Release(wmcda->dsBuf);
1033             wmcda->dsBuf = NULL;
1034         }
1035         if (wmcda->dsObj) {
1036             IDirectSound_Release(wmcda->dsObj);
1037             wmcda->dsObj = NULL;
1038         }
1039     }
1040
1041     play.StartingM = start / CDFRAMES_PERMIN;
1042     play.StartingS = (start / CDFRAMES_PERSEC) % 60;
1043     play.StartingF = start % CDFRAMES_PERSEC;
1044     play.EndingM   = end / CDFRAMES_PERMIN;
1045     play.EndingS   = (end / CDFRAMES_PERSEC) % 60;
1046     play.EndingF   = end % CDFRAMES_PERSEC;
1047     if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_PLAY_AUDIO_MSF, &play, sizeof(play),
1048                          NULL, 0, &br, NULL)) {
1049         wmcda->hCallback = NULL;
1050         ret = MCIERR_HARDWARE;
1051     }
1052     /* The independent CD player has no means to signal MCI_NOTIFY when it's done.
1053      * Native sends a notification with MCI_WAIT only. */
1054     return ret;
1055 }
1056
1057 /**************************************************************************
1058  *                              MCICDA_Stop                     [internal]
1059  */
1060 static DWORD MCICDA_Stop(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1061 {
1062     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
1063     HANDLE              oldcb;
1064     DWORD               br;
1065
1066     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1067
1068     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
1069
1070     oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1071     if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
1072
1073     if (wmcda->hThread != 0) {
1074         SetEvent(wmcda->stopEvent);
1075         WaitForSingleObject(wmcda->hThread, INFINITE);
1076
1077         CloseHandle(wmcda->hThread);
1078         wmcda->hThread = 0;
1079         CloseHandle(wmcda->stopEvent);
1080         wmcda->stopEvent = 0;
1081
1082         IDirectSoundBuffer_Release(wmcda->dsBuf);
1083         wmcda->dsBuf = NULL;
1084         IDirectSound_Release(wmcda->dsObj);
1085         wmcda->dsObj = NULL;
1086     }
1087     else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_STOP_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1088         return MCIERR_HARDWARE;
1089
1090     if ((dwFlags & MCI_NOTIFY) && lpParms)
1091         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1092     return 0;
1093 }
1094
1095 /**************************************************************************
1096  *                              MCICDA_Pause                    [internal]
1097  */
1098 static DWORD MCICDA_Pause(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1099 {
1100     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
1101     HANDLE              oldcb;
1102     DWORD               br;
1103
1104     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1105
1106     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
1107
1108     oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
1109     if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);
1110
1111     if (wmcda->hThread != 0) {
1112         /* Don't bother calling stop if the playLoop thread has already stopped */
1113         if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
1114            FAILED(IDirectSoundBuffer_Stop(wmcda->dsBuf)))
1115             return MCIERR_HARDWARE;
1116     }
1117     else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1118         return MCIERR_HARDWARE;
1119
1120     if ((dwFlags & MCI_NOTIFY) && lpParms)
1121         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1122     return 0;
1123 }
1124
1125 /**************************************************************************
1126  *                              MCICDA_Resume                   [internal]
1127  */
1128 static DWORD MCICDA_Resume(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
1129 {
1130     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
1131     DWORD               br;
1132
1133     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1134
1135     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
1136
1137     if (wmcda->hThread != 0) {
1138         /* Don't restart if the playLoop thread has already stopped */
1139         if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
1140            FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING)))
1141             return MCIERR_HARDWARE;
1142     }
1143     else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_RESUME_AUDIO, NULL, 0, NULL, 0, &br, NULL))
1144         return MCIERR_HARDWARE;
1145
1146     if ((dwFlags & MCI_NOTIFY) && lpParms)
1147         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1148     return 0;
1149 }
1150
1151 /**************************************************************************
1152  *                              MCICDA_Seek                     [internal]
1153  */
1154 static DWORD MCICDA_Seek(UINT wDevID, DWORD dwFlags, LPMCI_SEEK_PARMS lpParms)
1155 {
1156     DWORD                       at;
1157     WINE_MCICDAUDIO*            wmcda = MCICDA_GetOpenDrv(wDevID);
1158     CDROM_SEEK_AUDIO_MSF        seek;
1159     DWORD                       br, position, ret;
1160     CDROM_TOC                   toc;
1161
1162     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1163
1164     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
1165     if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
1166
1167     position = dwFlags & (MCI_SEEK_TO_START|MCI_SEEK_TO_END|MCI_TO);
1168     if (!position)              return MCIERR_MISSING_PARAMETER;
1169     if (position&(position-1))  return MCIERR_FLAGS_NOT_COMPATIBLE;
1170
1171     /* Stop sends MCI_NOTIFY_ABORTED when needed.
1172      * Tests show that native first sends ABORTED and reads the TOC,
1173      * then only checks the position flags, then stops and seeks. */
1174     MCICDA_Stop(wDevID, MCI_WAIT, 0);
1175
1176     if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
1177                          &toc, sizeof(toc), &br, NULL)) {
1178         WARN("error reading TOC !\n");
1179         return MCICDA_GetError(wmcda);
1180     }
1181     switch (position) {
1182     case MCI_SEEK_TO_START:
1183         TRACE("Seeking to start\n");
1184         at = FRAME_OF_TOC(toc,toc.FirstTrack);
1185         if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1186           return ret;
1187         break;
1188     case MCI_SEEK_TO_END:
1189         TRACE("Seeking to end\n");
1190         /* End is prior to lead-out
1191          * yet Win9X seeks to even one frame less than that. */
1192         at = FRAME_OF_TOC(toc, toc.LastTrack + 1) - 1;
1193         if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1194           return ret;
1195         break;
1196     case MCI_TO:
1197         TRACE("Seeking to %u\n", lpParms->dwTo);
1198         at = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
1199         if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
1200           return ret;
1201         break;
1202     default:
1203         return MCIERR_FLAGS_NOT_COMPATIBLE;
1204     }
1205
1206     {
1207         seek.M = at / CDFRAMES_PERMIN;
1208         seek.S = (at / CDFRAMES_PERSEC) % 60;
1209         seek.F = at % CDFRAMES_PERSEC;
1210         if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_SEEK_AUDIO_MSF, &seek, sizeof(seek),
1211                              NULL, 0, &br, NULL))
1212             return MCIERR_HARDWARE;
1213     }
1214
1215     if (dwFlags & MCI_NOTIFY)
1216         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1217     return 0;
1218 }
1219
1220 /**************************************************************************
1221  *                              MCICDA_SetDoor                  [internal]
1222  */
1223 static DWORD    MCICDA_SetDoor(UINT wDevID, BOOL open)
1224 {
1225     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
1226     DWORD               br;
1227
1228     TRACE("(%04x, %s) !\n", wDevID, (open) ? "OPEN" : "CLOSE");
1229
1230     if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;
1231
1232     if (!DeviceIoControl(wmcda->handle,
1233                          (open) ? IOCTL_STORAGE_EJECT_MEDIA : IOCTL_STORAGE_LOAD_MEDIA,
1234                          NULL, 0, NULL, 0, &br, NULL))
1235         return MCIERR_HARDWARE;
1236
1237     return 0;
1238 }
1239
1240 /**************************************************************************
1241  *                              MCICDA_Set                      [internal]
1242  */
1243 static DWORD MCICDA_Set(UINT wDevID, DWORD dwFlags, LPMCI_SET_PARMS lpParms)
1244 {
1245     WINE_MCICDAUDIO*    wmcda = MCICDA_GetOpenDrv(wDevID);
1246
1247     TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1248
1249     if (wmcda == NULL)  return MCIERR_INVALID_DEVICE_ID;
1250
1251     if (dwFlags & MCI_SET_DOOR_OPEN) {
1252         MCICDA_SetDoor(wDevID, TRUE);
1253     }
1254     if (dwFlags & MCI_SET_DOOR_CLOSED) {
1255         MCICDA_SetDoor(wDevID, FALSE);
1256     }
1257
1258     /* only functions which require valid lpParms below this line ! */
1259     if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
1260     /*
1261       TRACE("dwTimeFormat=%08lX\n", lpParms->dwTimeFormat);
1262     */
1263     if (dwFlags & MCI_SET_TIME_FORMAT) {
1264         switch (lpParms->dwTimeFormat) {
1265         case MCI_FORMAT_MILLISECONDS:
1266             TRACE("MCI_FORMAT_MILLISECONDS !\n");
1267             break;
1268         case MCI_FORMAT_MSF:
1269             TRACE("MCI_FORMAT_MSF !\n");
1270             break;
1271         case MCI_FORMAT_TMSF:
1272             TRACE("MCI_FORMAT_TMSF !\n");
1273             break;
1274         default:
1275             return MCIERR_BAD_TIME_FORMAT;
1276         }
1277         wmcda->dwTimeFormat = lpParms->dwTimeFormat;
1278     }
1279     if (dwFlags & MCI_SET_AUDIO) /* one xp machine ignored it */
1280         TRACE("SET_AUDIO %X %x\n", dwFlags, lpParms->dwAudio);
1281
1282     if (dwFlags & MCI_NOTIFY)
1283         MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1284     return 0;
1285 }
1286
1287 /**************************************************************************
1288  *                      DriverProc (MCICDA.@)
1289  */
1290 LRESULT CALLBACK MCICDA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
1291                                    LPARAM dwParam1, LPARAM dwParam2)
1292 {
1293     switch(wMsg) {
1294     case DRV_LOAD:              return 1;
1295     case DRV_FREE:              return 1;
1296     case DRV_OPEN:              return MCICDA_drvOpen((LPCWSTR)dwParam1, (LPMCI_OPEN_DRIVER_PARMSW)dwParam2);
1297     case DRV_CLOSE:             return MCICDA_drvClose(dwDevID);
1298     case DRV_ENABLE:            return 1;
1299     case DRV_DISABLE:           return 1;
1300     case DRV_QUERYCONFIGURE:    return 1;
1301     case DRV_CONFIGURE:         MessageBoxA(0, "MCI audio CD driver !", "Wine Driver", MB_OK); return 1;
1302     case DRV_INSTALL:           return DRVCNF_RESTART;
1303     case DRV_REMOVE:            return DRVCNF_RESTART;
1304     }
1305
1306     if (dwDevID == 0xFFFFFFFF) return MCIERR_UNSUPPORTED_FUNCTION;
1307
1308     switch (wMsg) {
1309     case MCI_OPEN_DRIVER:       return MCICDA_Open(dwDevID, dwParam1, (LPMCI_OPEN_PARMSW)dwParam2);
1310     case MCI_CLOSE_DRIVER:      return MCICDA_Close(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1311     case MCI_GETDEVCAPS:        return MCICDA_GetDevCaps(dwDevID, dwParam1, (LPMCI_GETDEVCAPS_PARMS)dwParam2);
1312     case MCI_INFO:              return MCICDA_Info(dwDevID, dwParam1, (LPMCI_INFO_PARMSW)dwParam2);
1313     case MCI_STATUS:            return MCICDA_Status(dwDevID, dwParam1, (LPMCI_STATUS_PARMS)dwParam2);
1314     case MCI_SET:               return MCICDA_Set(dwDevID, dwParam1, (LPMCI_SET_PARMS)dwParam2);
1315     case MCI_PLAY:              return MCICDA_Play(dwDevID, dwParam1, (LPMCI_PLAY_PARMS)dwParam2);
1316     case MCI_STOP:              return MCICDA_Stop(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1317     case MCI_PAUSE:             return MCICDA_Pause(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1318     case MCI_RESUME:            return MCICDA_Resume(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
1319     case MCI_SEEK:              return MCICDA_Seek(dwDevID, dwParam1, (LPMCI_SEEK_PARMS)dwParam2);
1320     /* commands that should report an error as they are not supported in
1321      * the native version */
1322     case MCI_RECORD:
1323     case MCI_LOAD:
1324     case MCI_SAVE:
1325         return MCIERR_UNSUPPORTED_FUNCTION;
1326     case MCI_BREAK:
1327     case MCI_FREEZE:
1328     case MCI_PUT:
1329     case MCI_REALIZE:
1330     case MCI_UNFREEZE:
1331     case MCI_UPDATE:
1332     case MCI_WHERE:
1333     case MCI_STEP:
1334     case MCI_SPIN:
1335     case MCI_ESCAPE:
1336     case MCI_COPY:
1337     case MCI_CUT:
1338     case MCI_DELETE:
1339     case MCI_PASTE:
1340     case MCI_WINDOW:
1341         TRACE("Unsupported command [0x%x]\n", wMsg);
1342         break;
1343     case MCI_OPEN:
1344     case MCI_CLOSE:
1345         ERR("Shouldn't receive a MCI_OPEN or CLOSE message\n");
1346         break;
1347     default:
1348         TRACE("Sending msg [0x%x] to default driver proc\n", wMsg);
1349         return DefDriverProc(dwDevID, hDriv, wMsg, dwParam1, dwParam2);
1350     }
1351     return MCIERR_UNRECOGNIZED_COMMAND;
1352 }
1353
1354 /*-----------------------------------------------------------------------*/