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