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