mcicda.c 47.5 KB
Newer Older
Alexandre Julliard's avatar
Alexandre Julliard committed
1
/*
2
 * MCI driver for audio CD (MCICDA)
Alexandre Julliard's avatar
Alexandre Julliard committed
3
 *
4 5
 * Copyright 1994    Martin Ayotte
 * Copyright 1998-99 Eric Pouech
6
 * Copyright 2000    Andreas Mohr
7 8 9 10 11 12 13 14 15 16 17 18 19
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * 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
Alexandre Julliard's avatar
Alexandre Julliard committed
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 36
#include "winioctl.h"
#include "ntddcdrm.h"
37
#include "winternl.h"
38
#include "wine/debug.h"
39
#include "wine/unicode.h"
40
#include "dsound.h"
Alexandre Julliard's avatar
Alexandre Julliard committed
41

42
WINE_DEFAULT_DEBUG_CHANNEL(mcicda);
43

44 45 46 47 48
#define CDFRAMES_PERSEC                 75
#define CDFRAMES_PERMIN                 (CDFRAMES_PERSEC * 60)
#define FRAME_OF_ADDR(a) ((a)[1] * CDFRAMES_PERMIN + (a)[2] * CDFRAMES_PERSEC + (a)[3])
#define FRAME_OF_TOC(toc, idx)  FRAME_OF_ADDR((toc).TrackData[idx - (toc).FirstTrack].Address)

49 50 51 52 53 54 55 56
/* Defined by red-book standard; do not change! */
#define RAW_SECTOR_SIZE  (2352)

/* Must be >= RAW_SECTOR_SIZE */
#define CDDA_FRAG_SIZE   (32768)
/* Must be >= 2 */
#define CDDA_FRAG_COUNT  (3)

Alexandre Julliard's avatar
Alexandre Julliard committed
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 67 68 69 70 71 72 73 74

    /* The following are used for digital playback only */
    HANDLE hThread;
    HANDLE stopEvent;
    DWORD start, end;

    IDirectSound *dsObj;
    IDirectSoundBuffer *dsBuf;

    CRITICAL_SECTION cs;
75
} WINE_MCICDAUDIO;
Alexandre Julliard's avatar
Alexandre Julliard committed
76 77 78

/*-----------------------------------------------------------------------*/

79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143
typedef HRESULT(WINAPI*LPDIRECTSOUNDCREATE)(LPCGUID,LPDIRECTSOUND*,LPUNKNOWN);
static LPDIRECTSOUNDCREATE pDirectSoundCreate;

static DWORD CALLBACK MCICDA_playLoop(void *ptr)
{
    WINE_MCICDAUDIO *wmcda = (WINE_MCICDAUDIO*)ptr;
    DWORD lastPos, curPos, endPos, br;
    void *cdData;
    DWORD lockLen, fragLen;
    DSBCAPS caps;
    RAW_READ_INFO rdInfo;
    HRESULT hr = DS_OK;

    memset(&caps, 0, sizeof(caps));
    caps.dwSize = sizeof(caps);
    hr = IDirectSoundBuffer_GetCaps(wmcda->dsBuf, &caps);

    fragLen = caps.dwBufferBytes/CDDA_FRAG_COUNT;
    curPos = lastPos = 0;
    endPos = ~0u;
    while (SUCCEEDED(hr) && endPos != lastPos &&
           WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0) {
        hr = IDirectSoundBuffer_GetCurrentPosition(wmcda->dsBuf, &curPos, NULL);
        if ((curPos-lastPos+caps.dwBufferBytes)%caps.dwBufferBytes < fragLen) {
            Sleep(1);
            continue;
        }

        EnterCriticalSection(&wmcda->cs);
        rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
        rdInfo.SectorCount = min(fragLen/RAW_SECTOR_SIZE, wmcda->end-wmcda->start);
        rdInfo.TrackMode = CDDA;

        hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
        if (hr == DSERR_BUFFERLOST) {
            if(FAILED(IDirectSoundBuffer_Restore(wmcda->dsBuf)) ||
               FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING))) {
                LeaveCriticalSection(&wmcda->cs);
                break;
            }
            hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, lastPos, fragLen, &cdData, &lockLen, NULL, NULL, 0);
        }

        if (SUCCEEDED(hr)) {
            if (rdInfo.SectorCount > 0) {
                if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_RAW_READ, &rdInfo, sizeof(rdInfo), cdData, lockLen, &br, NULL))
                    WARN("CD read failed at sector %d: 0x%x\n", wmcda->start, GetLastError());
            }
            if (rdInfo.SectorCount*RAW_SECTOR_SIZE < lockLen) {
                if(endPos == ~0u) endPos = lastPos;
                memset((BYTE*)cdData + rdInfo.SectorCount*RAW_SECTOR_SIZE, 0,
                       lockLen - rdInfo.SectorCount*RAW_SECTOR_SIZE);
            }
            hr = IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);
        }

        lastPos += fragLen;
        lastPos %= caps.dwBufferBytes;
        wmcda->start += rdInfo.SectorCount;

        LeaveCriticalSection(&wmcda->cs);
    }
    IDirectSoundBuffer_Stop(wmcda->dsBuf);
    SetEvent(wmcda->stopEvent);

144 145 146 147 148
    /* A design bug in native: the independent CD player called by the
     * MCI has no means to signal end of playing, therefore the MCI
     * notification is left hanging.  MCI_NOTIFY_SUPERSEDED will be
     * signaled by the next command that has MCI_NOTIFY set (or
     * MCI_NOTIFY_ABORTED for MCI_PLAY). */
149

150 151 152 153 154
    return 0;
}



155
/**************************************************************************
156
 * 				MCICDA_drvOpen			[internal]
157
 */
158
static	DWORD	MCICDA_drvOpen(LPCWSTR str, LPMCI_OPEN_DRIVER_PARMSW modp)
159
{
160
    static HMODULE dsHandle;
161 162 163
    WINE_MCICDAUDIO*	wmcda;

    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 169 170

    if (!wmcda)
	return 0;

171 172 173 174 175 176
    if (!dsHandle) {
        dsHandle = LoadLibraryA("dsound.dll");
        if(dsHandle)
            pDirectSoundCreate = (LPDIRECTSOUNDCREATE)GetProcAddress(dsHandle, "DirectSoundCreate");
    }

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 197
	HeapFree(GetProcessHeap(), 0, wmcda);
	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 212
	return 0;
    }
213
    return wmcda;
214
}
Alexandre Julliard's avatar
Alexandre Julliard committed
215

216 217 218 219 220 221 222 223 224 225 226 227 228 229
/**************************************************************************
 *				MCICDA_mciNotify		[internal]
 *
 * Notifications in MCI work like a 1-element queue.
 * Each new notification request supersedes the previous one.
 */
static void MCICDA_Notify(DWORD_PTR hWndCallBack, WINE_MCICDAUDIO* wmcda, UINT wStatus)
{
    MCIDEVICEID wDevID = wmcda->wNotifyDeviceID;
    HANDLE old = InterlockedExchangePointer(&wmcda->hCallback, NULL);
    if (old) mciDriverNotify(old, wDevID, MCI_NOTIFY_SUPERSEDED);
    mciDriverNotify(HWND_32(LOWORD(hWndCallBack)), wDevID, wStatus);
}

Alexandre Julliard's avatar
Alexandre Julliard committed
230
/**************************************************************************
231
 * 				MCICDA_GetStatus		[internal]
232
 */
233
static	DWORD    MCICDA_GetStatus(WINE_MCICDAUDIO* wmcda)
Alexandre Julliard's avatar
Alexandre Julliard committed
234
{
235 236 237 238 239 240
    CDROM_SUB_Q_DATA_FORMAT     fmt;
    SUB_Q_CHANNEL_DATA          data;
    DWORD                       br;
    DWORD                       mode = MCI_MODE_NOT_READY;

    fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258
    if(wmcda->hThread != 0) {
        DWORD status;
        HRESULT hr;

        hr = IDirectSoundBuffer_GetStatus(wmcda->dsBuf, &status);
        if(SUCCEEDED(hr)) {
            if(!(status&DSBSTATUS_PLAYING)) {
                if(WaitForSingleObject(wmcda->stopEvent, 0) == WAIT_OBJECT_0)
                    mode = MCI_MODE_STOP;
                else
                    mode = MCI_MODE_PAUSE;
            }
            else
                mode = MCI_MODE_PLAY;
        }
    }
    else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
                              &data, sizeof(data), &br, NULL)) {
259
        if (GetLastError() == ERROR_NOT_READY) mode = MCI_MODE_OPEN;
260 261 262 263 264
    } else {
        switch (data.CurrentPosition.Header.AudioStatus)
        {
        case AUDIO_STATUS_IN_PROGRESS:          mode = MCI_MODE_PLAY;   break;
        case AUDIO_STATUS_PAUSED:               mode = MCI_MODE_PAUSE;  break;
265
        case AUDIO_STATUS_NO_STATUS:
266 267 268 269 270 271
        case AUDIO_STATUS_PLAY_COMPLETE:        mode = MCI_MODE_STOP;   break;
        case AUDIO_STATUS_PLAY_ERROR:
        case AUDIO_STATUS_NOT_SUPPORTED:
        default:
            break;
        }
272
    }
273
    return mode;
Alexandre Julliard's avatar
Alexandre Julliard committed
274 275 276
}

/**************************************************************************
277
 * 				MCICDA_GetError			[internal]
278
 */
279
static	int	MCICDA_GetError(WINE_MCICDAUDIO* wmcda)
Alexandre Julliard's avatar
Alexandre Julliard committed
280
{
281 282
    switch (GetLastError())
    {
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;
Alexandre Julliard's avatar
Alexandre Julliard committed
290 291
}

Alexandre Julliard's avatar
Alexandre Julliard committed
292
/**************************************************************************
293
 * 			MCICDA_CalcFrame			[internal]
294
 */
295
static DWORD MCICDA_CalcFrame(WINE_MCICDAUDIO* wmcda, DWORD dwTime)
Alexandre Julliard's avatar
Alexandre Julliard committed
296
{
297
    DWORD	dwFrame = 0;
298
    UINT	wTrack;
299 300 301
    CDROM_TOC   toc;
    DWORD       br;
    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 310
	break;
    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 314 315 316
	dwFrame += CDFRAMES_PERMIN * MCI_MSF_MINUTE(dwTime);
	dwFrame += CDFRAMES_PERSEC * MCI_MSF_SECOND(dwTime);
	dwFrame += MCI_MSF_FRAME(dwTime);
	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 322 323 324 325
                             &toc, sizeof(toc), &br, NULL))
            return 0;
        if (wTrack < toc.FirstTrack || wTrack > toc.LastTrack)
            return 0;
        TRACE("MSF %02u-%02u:%02u:%02u\n",
326
              MCI_TMSF_TRACK(dwTime), MCI_TMSF_MINUTE(dwTime),
327 328 329
              MCI_TMSF_SECOND(dwTime), MCI_TMSF_FRAME(dwTime));
        addr = toc.TrackData[wTrack - toc.FirstTrack].Address;
        TRACE("TMSF trackpos[%u]=%d:%d:%d\n",
330 331 332 333
              wTrack, addr[1], addr[2], addr[3]);
        dwFrame = CDFRAMES_PERMIN * (addr[1] + MCI_TMSF_MINUTE(dwTime)) +
            CDFRAMES_PERSEC * (addr[2] + MCI_TMSF_SECOND(dwTime)) +
            addr[3] + MCI_TMSF_FRAME(dwTime);
334 335 336
	break;
    }
    return dwFrame;
Alexandre Julliard's avatar
Alexandre Julliard committed
337 338
}

Alexandre Julliard's avatar
Alexandre Julliard committed
339
/**************************************************************************
340
 * 			MCICDA_CalcTime				[internal]
341
 */
342
static DWORD MCICDA_CalcTime(WINE_MCICDAUDIO* wmcda, DWORD tf, DWORD dwFrame, LPDWORD lpRet)
Alexandre Julliard's avatar
Alexandre Julliard committed
343
{
344
    DWORD	dwTime = 0;
345 346 347 348
    UINT	wTrack;
    UINT	wMinutes;
    UINT	wSeconds;
    UINT	wFrames;
349 350
    CDROM_TOC   toc;
    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 360 361 362
	break;
    case MCI_FORMAT_MSF:
	wMinutes = dwFrame / CDFRAMES_PERMIN;
	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 373
                             &toc, sizeof(toc), &br, NULL))
            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 380 381
	    *lpRet = 0;
	    return 0;
	}
382 383
	for (wTrack = toc.FirstTrack; wTrack <= toc.LastTrack; wTrack++) {
	    if (FRAME_OF_TOC(toc, wTrack) > dwFrame)
384
		break;
385
	}
386 387
        wTrack--;
	dwFrame -= FRAME_OF_TOC(toc, wTrack);
388 389
	wMinutes = dwFrame / CDFRAMES_PERMIN;
	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 395 396
	break;
    }
    return dwTime;
Alexandre Julliard's avatar
Alexandre Julliard committed
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 407
    MCIDEVICEID		dwDeviceID;
    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 417

    dwDeviceID = lpOpenParms->wDeviceID;

418 419
    if (wmcda->nUseCount > 0) {
	/* The driver is already open on this channel */
420 421
	/* If the driver was opened shareable before and this open specifies */
	/* shareable then increment the use count */
422 423
	if (wmcda->fShareable && (dwFlags & MCI_OPEN_SHAREABLE))
	    ++wmcda->nUseCount;
424 425 426
	else
	    return MCIERR_MUST_USE_SHAREABLE;
    } else {
427 428
	wmcda->nUseCount = 1;
	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 438 439 440
        /* Only the first letter counts since w2k
         * Win9x-NT accept only d: and w98SE accepts d:\foobar as well.
         * Play d:\Track03.cda plays from the first track, not #3. */
        if (!isalpha(lpOpenParms->lpstrElementName[0]))
441
        {
442
            ret = MCIERR_INVALID_FILE;
443 444 445
            goto the_error;
        }
        drive = toupper(lpOpenParms->lpstrElementName[0]);
446 447
        root[0] = drive; root[1] = ':'; root[2] = '\\'; root[3] = '\0';
        if (GetDriveTypeW(root) != DRIVE_CDROM)
448
        {
449
            ret = MCIERR_INVALID_FILE;
450 451 452 453 454
            goto the_error;
        }
    }
    else
    {
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 461 462 463 464 465
            {
                drive = root[0];
                break;
            }
        }
        if (!drive)
        {
466
            ret = MCIERR_CANNOT_LOAD_DRIVER; /* drvOpen should return this */
467 468
            goto the_error;
        }
469 470
    }

471
    wmcda->wNotifyDeviceID = dwDeviceID;
472
    wmcda->dwTimeFormat = MCI_FORMAT_MSF;
473

474
    /* now, open the handle */
475 476
    root[0] = root[1] = '\\'; root[2] = '.'; root[3] = '\\'; root[4] = drive; root[5] = ':'; root[6] = '\0';
    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 484

    if (dwFlags & MCI_NOTIFY) {
	mciDriverNotify(HWND_32(LOWORD(lpOpenParms->dwCallback)),
485
			dwDeviceID, MCI_NOTIFY_SUCCESSFUL);
486 487
    }
    return 0;
488 489 490 491

 the_error:
    --wmcda->nUseCount;
    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 506
    MCICDA_Stop(wDevID, MCI_WAIT, NULL);

507 508
    if (--wmcda->nUseCount == 0) {
	CloseHandle(wmcda->handle);
509
    }
510 511
    if ((dwParam & MCI_NOTIFY) && lpParms)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
512 513 514 515
    return 0;
}

/**************************************************************************
516
 * 				MCICDA_GetDevCaps		[internal]
517
 */
518
static DWORD MCICDA_GetDevCaps(UINT wDevID, DWORD dwFlags,
519 520
				   LPMCI_GETDEVCAPS_PARMS lpParms)
{
521
    WINE_MCICDAUDIO* 	wmcda = (WINE_MCICDAUDIO*)mciGetDriverData(wDevID);
522 523
    DWORD	ret = 0;

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 535
	    lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
	    ret = MCI_RESOURCE_RETURNED;
536 537
	    break;
	case MCI_GETDEVCAPS_HAS_AUDIO:
538 539
	    lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
	    ret = MCI_RESOURCE_RETURNED;
540 541
	    break;
	case MCI_GETDEVCAPS_HAS_VIDEO:
542 543
	    lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
	    ret = MCI_RESOURCE_RETURNED;
544 545
	    break;
	case MCI_GETDEVCAPS_DEVICE_TYPE:
546 547
	    lpParms->dwReturn = MAKEMCIRESOURCE(MCI_DEVTYPE_CD_AUDIO, MCI_DEVTYPE_CD_AUDIO);
	    ret = MCI_RESOURCE_RETURNED;
548 549
	    break;
	case MCI_GETDEVCAPS_USES_FILES:
550 551
	    lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
	    ret = MCI_RESOURCE_RETURNED;
552 553
	    break;
	case MCI_GETDEVCAPS_COMPOUND_DEVICE:
554 555
	    lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
	    ret = MCI_RESOURCE_RETURNED;
556 557
	    break;
	case MCI_GETDEVCAPS_CAN_EJECT:
558 559
	    lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
	    ret = MCI_RESOURCE_RETURNED;
560 561
	    break;
	case MCI_GETDEVCAPS_CAN_PLAY:
562 563
	    lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
	    ret = MCI_RESOURCE_RETURNED;
564 565
	    break;
	case MCI_GETDEVCAPS_CAN_SAVE:
566 567
	    lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
	    ret = MCI_RESOURCE_RETURNED;
568 569
	    break;
	default:
570 571
            WARN("Unsupported %x devCaps item\n", lpParms->dwItem);
	    return MCIERR_UNSUPPORTED_FUNCTION;
572
	}
573 574
    } else {
	TRACE("No GetDevCaps-Item !\n");
575
	return MCIERR_MISSING_PARAMETER;
576
    }
577
    TRACE("lpParms->dwReturn=%08X;\n", lpParms->dwReturn);
578 579 580
    if (dwFlags & MCI_NOTIFY) {
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
    }
581
    return ret;
582 583
}

584 585
static DWORD CDROM_Audio_GetSerial(CDROM_TOC* toc)
{
586
    DWORD serial = 0;
587 588 589
    int i;
    WORD wMagic;
    DWORD dwStart, dwEnd;
590

591 592 593 594 595 596 597 598 599 600 601
    /*
     * wMagic collects the wFrames from track 1
     * dwStart, dwEnd collect the beginning and end of the disc respectively, in
     * frames.
     * There it is collected for correcting the serial when there are less than
     * 3 tracks.
     */
    wMagic = toc->TrackData[0].Address[3];
    dwStart = FRAME_OF_TOC(*toc, toc->FirstTrack);

    for (i = 0; i <= toc->LastTrack - toc->FirstTrack; i++) {
602
        serial += (toc->TrackData[i].Address[1] << 16) |
603 604 605
            (toc->TrackData[i].Address[2] << 8) | toc->TrackData[i].Address[3];
    }
    dwEnd = FRAME_OF_TOC(*toc, toc->LastTrack + 1);
606

607 608 609 610 611
    if (toc->LastTrack - toc->FirstTrack + 1 < 3)
        serial += wMagic + (dwEnd - dwStart);

    return serial;
}
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 627 628 629
    if (lpParms == NULL || lpParms->lpstrReturn == NULL)
	return MCIERR_NULL_PARAMETER_BLOCK;
    if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;

630
    TRACE("buf=%p, len=%u\n", lpParms->lpstrReturn, lpParms->dwRetSize);
631

632
    if (dwFlags & MCI_INFO_PRODUCT) {
633 634
        static const WCHAR wszAudioCd[] = {'W','i','n','e','\'','s',' ','a','u','d','i','o',' ','C','D',0};
        str = wszAudioCd;
635 636 637
    } else if (dwFlags & MCI_INFO_MEDIA_UPC) {
	ret = MCIERR_NO_IDENTITY;
    } else if (dwFlags & MCI_INFO_MEDIA_IDENTITY) {
638 639 640
	DWORD	    res = 0;
        CDROM_TOC   toc;
        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 651
	str = buffer;
    } else {
652
	WARN("Don't know this info command (%u)\n", dwFlags);
653
	ret = MCIERR_MISSING_PARAMETER;
654
    }
655 656 657 658 659 660 661 662
    if (!ret) {
	TRACE("=> %s\n", debugstr_w(str));
	if (lpParms->dwRetSize) {
	    WCHAR zero = 0;
	    /* FIXME? Since NT, mciwave, mciseq and mcicda set dwRetSize
	     *        to the number of characters written, excluding \0. */
	    lstrcpynW(lpParms->lpstrReturn, str ? str : &zero, lpParms->dwRetSize);
	} else ret = MCIERR_PARAM_OVERFLOW;
663
    }
664 665
    if (MMSYSERR_NOERROR==ret && (dwFlags & MCI_NOTIFY))
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
666
    return ret;
667
}
Alexandre Julliard's avatar
Alexandre Julliard committed
668 669

/**************************************************************************
670
 * 				MCICDA_Status			[internal]
671
 */
672
static DWORD MCICDA_Status(UINT wDevID, DWORD dwFlags, LPMCI_STATUS_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
673
{
674 675 676 677 678 679
    WINE_MCICDAUDIO*	        wmcda = MCICDA_GetOpenDrv(wDevID);
    DWORD	                ret = 0;
    CDROM_SUB_Q_DATA_FORMAT     fmt;
    SUB_Q_CHANNEL_DATA          data;
    CDROM_TOC                   toc;
    DWORD                       br;
680

681
    TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
682

683 684 685 686
    if (lpParms == NULL) return MCIERR_NULL_PARAMETER_BLOCK;
    if (wmcda == NULL) return MCIERR_INVALID_DEVICE_ID;

    if (dwFlags & MCI_STATUS_ITEM) {
687
	TRACE("dwItem = %x\n", lpParms->dwItem);
688 689
	switch (lpParms->dwItem) {
	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 693
                                 &data, sizeof(data), &br, NULL))
            {
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 700
	    break;
	case MCI_STATUS_LENGTH:
701
            if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
702 703 704
                                 &toc, sizeof(toc), &br, NULL)) {
                WARN("error reading TOC !\n");
                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 713
		/* Windows returns one frame less than the total track length for the
		   last track on the CD.  See CDDB HOWTO.  Verified on Win95OSR2. */
714
		if (lpParms->dwTrack == toc.LastTrack)
715
		    lpParms->dwReturn--;
716
	    } else {
717 718 719
		/* Sum of the lengths of all of the tracks.  Inherits the
		   'off by one frame' behavior from the length of the last track.
		   See above comment. */
720 721
		lpParms->dwReturn = FRAME_OF_TOC(toc, toc.LastTrack + 1) -
                    FRAME_OF_TOC(toc, toc.FirstTrack) - 1;
722
	    }
723 724
	    lpParms->dwReturn = MCICDA_CalcTime(wmcda,
						 (wmcda->dwTimeFormat == MCI_FORMAT_TMSF)
725 726 727
						    ? MCI_FORMAT_MSF : wmcda->dwTimeFormat,
						 lpParms->dwReturn,
						 &ret);
728
            TRACE("LENGTH=%lu\n", lpParms->dwReturn);
729 730
	    break;
	case MCI_STATUS_MODE:
731
            lpParms->dwReturn = MCICDA_GetStatus(wmcda);
732
            TRACE("MCI_STATUS_MODE=%08lX\n", lpParms->dwReturn);
733 734 735 736
	    lpParms->dwReturn = MAKEMCIRESOURCE(lpParms->dwReturn, lpParms->dwReturn);
	    ret = MCI_RESOURCE_RETURNED;
	    break;
	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 741 742
	    ret = MCI_RESOURCE_RETURNED;
	    break;
	case MCI_STATUS_NUMBER_OF_TRACKS:
743
            if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
744 745 746 747 748
                                 &toc, sizeof(toc), &br, NULL)) {
                WARN("error reading TOC !\n");
                return MCICDA_GetError(wmcda);
	    }
	    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 753
	    break;
	case MCI_STATUS_POSITION:
754 755
            switch (dwFlags & (MCI_STATUS_START | MCI_TRACK)) {
            case MCI_STATUS_START:
756
                if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
757 758 759 760 761
                                     &toc, sizeof(toc), &br, NULL)) {
                    WARN("error reading TOC !\n");
                    return MCICDA_GetError(wmcda);
                }
		lpParms->dwReturn = FRAME_OF_TOC(toc, toc.FirstTrack);
762
		TRACE("get MCI_STATUS_START !\n");
763 764
                break;
            case MCI_TRACK:
765
                if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
766 767 768 769 770
                                     &toc, sizeof(toc), &br, NULL)) {
                    WARN("error reading TOC !\n");
                    return MCICDA_GetError(wmcda);
                }
		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 775
                break;
            case 0:
776
                fmt.Format = IOCTL_CDROM_CURRENT_POSITION;
777
                if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_Q_CHANNEL, &fmt, sizeof(fmt),
778 779 780 781
                                     &data, sizeof(data), &br, NULL)) {
                    return MCICDA_GetError(wmcda);
                }
                lpParms->dwReturn = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
782 783 784
                break;
            default:
                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 789 790
	    break;
	case MCI_STATUS_READY:
	    TRACE("MCI_STATUS_READY !\n");
791 792 793 794 795 796 797 798 799 800
            switch (MCICDA_GetStatus(wmcda))
            {
            case MCI_MODE_NOT_READY:
            case MCI_MODE_OPEN:
                lpParms->dwReturn = MAKEMCIRESOURCE(FALSE, MCI_FALSE);
                break;
            default:
                lpParms->dwReturn = MAKEMCIRESOURCE(TRUE, MCI_TRUE);
                break;
            }
801 802 803 804
	    TRACE("MCI_STATUS_READY=%u!\n", LOWORD(lpParms->dwReturn));
	    ret = MCI_RESOURCE_RETURNED;
	    break;
	case MCI_STATUS_TIME_FORMAT:
805
	    lpParms->dwReturn = MAKEMCIRESOURCE(wmcda->dwTimeFormat, MCI_FORMAT_RETURN_BASE + wmcda->dwTimeFormat);
806 807 808
	    TRACE("MCI_STATUS_TIME_FORMAT=%08x!\n", LOWORD(lpParms->dwReturn));
	    ret = MCI_RESOURCE_RETURNED;
	    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 816 817 818 819
                                     &toc, sizeof(toc), &br, NULL)) {
                    WARN("error reading TOC !\n");
                    return MCICDA_GetError(wmcda);
                }
		if (lpParms->dwTrack < toc.FirstTrack || lpParms->dwTrack > toc.LastTrack)
820 821
		    ret = MCIERR_OUTOFRANGE;
		else
822 823
		    lpParms->dwReturn = (toc.TrackData[lpParms->dwTrack - toc.FirstTrack].Control & 0x04) ?
                                         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 828
	    break;
	default:
829
            FIXME("unknown command %08X !\n", lpParms->dwItem);
830
	    return MCIERR_UNSUPPORTED_FUNCTION;
831
	}
832
    } else return MCIERR_MISSING_PARAMETER;
833 834
    if ((dwFlags & MCI_NOTIFY) && HRESULT_CODE(ret)==0)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
835
    return ret;
Alexandre Julliard's avatar
Alexandre Julliard committed
836 837
}

838 839 840 841 842 843 844 845 846 847 848 849 850
/**************************************************************************
 * 				MCICDA_SkipDataTracks		[internal]
 */
static DWORD MCICDA_SkipDataTracks(WINE_MCICDAUDIO* wmcda,DWORD *frame)
{
  int i;
  DWORD br;
  CDROM_TOC toc;
  if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
                      &toc, sizeof(toc), &br, NULL)) {
    WARN("error reading TOC !\n");
    return MCICDA_GetError(wmcda);
  }
Jörg Höhle's avatar
Jörg Höhle committed
851 852 853 854 855
  if (*frame < FRAME_OF_TOC(toc,toc.FirstTrack) ||
      *frame >= FRAME_OF_TOC(toc,toc.LastTrack+1)) /* lead-out */
    return MCIERR_OUTOFRANGE;
  for(i=toc.LastTrack+1;i>toc.FirstTrack;i--)
    if ( FRAME_OF_TOC(toc, i) <= *frame ) break;
856 857
  /* i points to last track whose start address is not greater than frame.
   * Now skip non-audio tracks */
Jörg Höhle's avatar
Jörg Höhle committed
858
  for(;i<=toc.LastTrack;i++)
859 860 861 862 863 864
    if ( ! (toc.TrackData[i-toc.FirstTrack].Control & 4) )
      break;
  /* The frame will be an address in the next audio track or
   * address of lead-out. */
  if ( FRAME_OF_TOC(toc, i) > *frame )
    *frame = FRAME_OF_TOC(toc, i);
Jörg Höhle's avatar
Jörg Höhle committed
865 866 867
  /* Lead-out is an invalid seek position (on Linux as well). */
  if (*frame == FRAME_OF_TOC(toc,toc.LastTrack+1))
     (*frame)--;
868 869 870
  return 0;
}

Alexandre Julliard's avatar
Alexandre Julliard committed
871
/**************************************************************************
872
 * 				MCICDA_Play			[internal]
873
 */
874
static DWORD MCICDA_Play(UINT wDevID, DWORD dwFlags, LPMCI_PLAY_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
875
{
876 877
    WINE_MCICDAUDIO*	        wmcda = MCICDA_GetOpenDrv(wDevID);
    DWORD		        ret = 0, start, end;
878
    HANDLE                      oldcb;
879 880 881 882
    DWORD                       br;
    CDROM_PLAY_AUDIO_MSF        play;
    CDROM_SUB_Q_DATA_FORMAT     fmt;
    SUB_Q_CHANNEL_DATA          data;
883
    CDROM_TOC			toc;
884

885
    TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
886

887 888 889 890 891 892
    if (lpParms == NULL)
	return MCIERR_NULL_PARAMETER_BLOCK;

    if (wmcda == NULL)
	return MCIERR_INVALID_DEVICE_ID;

893 894 895 896 897 898
    if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
                         &toc, sizeof(toc), &br, NULL)) {
        WARN("error reading TOC !\n");
        return MCICDA_GetError(wmcda);
    }

899 900
    if (dwFlags & MCI_FROM) {
	start = MCICDA_CalcFrame(wmcda, lpParms->dwFrom);
901 902
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
	  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 908 909 910
                             &data, sizeof(data), &br, NULL)) {
            return MCICDA_GetError(wmcda);
        }
        start = FRAME_OF_ADDR(data.CurrentPosition.AbsoluteAddress);
911 912
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &start)) )
	  return ret;
913 914 915
    }
    if (dwFlags & MCI_TO) {
	end = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
916 917
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &end)) )
	  return ret;
918
	TRACE("MCI_TO=%08X -> %u\n", lpParms->dwTo, end);
919 920
    } else {
	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 926 927 928
    oldcb = InterlockedExchangePointer(&wmcda->hCallback,
	(dwFlags & MCI_NOTIFY) ? HWND_32(LOWORD(lpParms->dwCallback)) : NULL);
    if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);

Jörg Höhle's avatar
Jörg Höhle committed
929 930 931 932 933 934 935 936
    if (start == end || start == FRAME_OF_TOC(toc,toc.LastTrack+1)-1) {
        if (dwFlags & MCI_NOTIFY) {
            oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
            if (oldcb) mciDriverNotify(oldcb, wDevID, MCI_NOTIFY_SUCCESSFUL);
        }
        return MMSYSERR_NOERROR;
    }

937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010
    if (wmcda->hThread != 0) {
        SetEvent(wmcda->stopEvent);
        WaitForSingleObject(wmcda->hThread, INFINITE);

        CloseHandle(wmcda->hThread);
        wmcda->hThread = 0;
        CloseHandle(wmcda->stopEvent);
        wmcda->stopEvent = 0;

        IDirectSoundBuffer_Stop(wmcda->dsBuf);
        IDirectSoundBuffer_Release(wmcda->dsBuf);
        wmcda->dsBuf = NULL;
        IDirectSound_Release(wmcda->dsObj);
        wmcda->dsObj = NULL;
    }

    if (pDirectSoundCreate) {
        WAVEFORMATEX format;
        DSBUFFERDESC desc;
        DWORD lockLen;
        void *cdData;
        HRESULT hr;

        hr = pDirectSoundCreate(NULL, &wmcda->dsObj, NULL);
        if (SUCCEEDED(hr)) {
            IDirectSound_SetCooperativeLevel(wmcda->dsObj, GetDesktopWindow(), DSSCL_PRIORITY);

            /* The "raw" frame is relative to the start of the first track */
            wmcda->start = start - FRAME_OF_TOC(toc, toc.FirstTrack);
            wmcda->end = end - FRAME_OF_TOC(toc, toc.FirstTrack);

            memset(&format, 0, sizeof(format));
            format.wFormatTag = WAVE_FORMAT_PCM;
            format.nChannels = 2;
            format.nSamplesPerSec = 44100;
            format.wBitsPerSample = 16;
            format.nBlockAlign = format.nChannels * format.wBitsPerSample / 8;
            format.nAvgBytesPerSec = format.nSamplesPerSec * format.nBlockAlign;
            format.cbSize = 0;

            memset(&desc, 0, sizeof(desc));
            desc.dwSize = sizeof(desc);
            desc.dwFlags = DSBCAPS_GETCURRENTPOSITION2 | DSBCAPS_GLOBALFOCUS;
            desc.dwBufferBytes = (CDDA_FRAG_SIZE - (CDDA_FRAG_SIZE%RAW_SECTOR_SIZE)) * CDDA_FRAG_COUNT;
            desc.lpwfxFormat = &format;

            hr = IDirectSound_CreateSoundBuffer(wmcda->dsObj, &desc, &wmcda->dsBuf, NULL);
        }
        if (SUCCEEDED(hr)) {
            hr = IDirectSoundBuffer_Lock(wmcda->dsBuf, 0, 0, &cdData, &lockLen,
                                         NULL, NULL, DSBLOCK_ENTIREBUFFER);
        }
        if (SUCCEEDED(hr)) {
            RAW_READ_INFO rdInfo;
            int readok;

            rdInfo.DiskOffset.QuadPart = wmcda->start<<11;
            rdInfo.SectorCount = min(desc.dwBufferBytes/RAW_SECTOR_SIZE,
                                     wmcda->end-wmcda->start);
            rdInfo.TrackMode = CDDA;

            readok = DeviceIoControl(wmcda->handle, IOCTL_CDROM_RAW_READ,
                                     &rdInfo, sizeof(rdInfo), cdData, lockLen,
                                     &br, NULL);
            IDirectSoundBuffer_Unlock(wmcda->dsBuf, cdData, lockLen, NULL, 0);

            if (readok) {
                wmcda->start += rdInfo.SectorCount;
                wmcda->stopEvent = CreateEventA(NULL, TRUE, FALSE, NULL);
            }
            if (wmcda->stopEvent != 0)
                wmcda->hThread = CreateThread(NULL, 0, MCICDA_playLoop, wmcda, 0, &br);
            if (wmcda->hThread != 0) {
                hr = IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING);
1011 1012 1013 1014 1015 1016 1017
                if (SUCCEEDED(hr)) {
                    /* FIXME: implement MCI_WAIT and send notification only in that case */
                    if (0) {
                        oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
                        if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID,
                            FAILED(hr) ? MCI_NOTIFY_FAILURE : MCI_NOTIFY_SUCCESSFUL);
                    }
1018
                    return ret;
1019
                }
1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032 1033 1034 1035 1036 1037 1038 1039 1040 1041

                SetEvent(wmcda->stopEvent);
                WaitForSingleObject(wmcda->hThread, INFINITE);
                CloseHandle(wmcda->hThread);
                wmcda->hThread = 0;
            }
        }

        if (wmcda->stopEvent != 0) {
            CloseHandle(wmcda->stopEvent);
            wmcda->stopEvent = 0;
        }
        if (wmcda->dsBuf) {
            IDirectSoundBuffer_Release(wmcda->dsBuf);
            wmcda->dsBuf = NULL;
        }
        if (wmcda->dsObj) {
            IDirectSound_Release(wmcda->dsObj);
            wmcda->dsObj = NULL;
        }
    }

1042 1043 1044 1045 1046 1047
    play.StartingM = start / CDFRAMES_PERMIN;
    play.StartingS = (start / CDFRAMES_PERSEC) % 60;
    play.StartingF = start % CDFRAMES_PERSEC;
    play.EndingM   = end / CDFRAMES_PERMIN;
    play.EndingS   = (end / CDFRAMES_PERSEC) % 60;
    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 1054
    /* The independent CD player has no means to signal MCI_NOTIFY when it's done.
     * Native sends a notification with MCI_WAIT only. */
1055
    return ret;
Alexandre Julliard's avatar
Alexandre Julliard committed
1056 1057 1058
}

/**************************************************************************
1059
 * 				MCICDA_Stop			[internal]
1060
 */
1061
static DWORD MCICDA_Stop(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
1062
{
1063
    WINE_MCICDAUDIO*	wmcda = MCICDA_GetOpenDrv(wDevID);
1064
    HANDLE		oldcb;
1065 1066
    DWORD               br;

1067
    TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1068

1069
    if (wmcda == NULL)	return MCIERR_INVALID_DEVICE_ID;
1070

1071 1072 1073
    oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
    if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);

1074 1075 1076 1077 1078 1079 1080 1081 1082 1083 1084 1085 1086 1087 1088 1089
    if (wmcda->hThread != 0) {
        SetEvent(wmcda->stopEvent);
        WaitForSingleObject(wmcda->hThread, INFINITE);

        CloseHandle(wmcda->hThread);
        wmcda->hThread = 0;
        CloseHandle(wmcda->stopEvent);
        wmcda->stopEvent = 0;

        IDirectSoundBuffer_Release(wmcda->dsBuf);
        wmcda->dsBuf = NULL;
        IDirectSound_Release(wmcda->dsObj);
        wmcda->dsObj = NULL;
    }
    else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_STOP_AUDIO, NULL, 0, NULL, 0, &br, NULL))
        return MCIERR_HARDWARE;
1090

1091 1092
    if ((dwFlags & MCI_NOTIFY) && lpParms)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1093
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
1094 1095 1096
}

/**************************************************************************
1097
 * 				MCICDA_Pause			[internal]
1098
 */
1099
static DWORD MCICDA_Pause(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
1100
{
1101
    WINE_MCICDAUDIO*	wmcda = MCICDA_GetOpenDrv(wDevID);
1102
    HANDLE		oldcb;
1103 1104
    DWORD               br;

1105
    TRACE("(%04X, %08X, %p);\n", wDevID, dwFlags, lpParms);
1106

1107
    if (wmcda == NULL)	return MCIERR_INVALID_DEVICE_ID;
1108

1109 1110 1111
    oldcb = InterlockedExchangePointer(&wmcda->hCallback, NULL);
    if (oldcb) mciDriverNotify(oldcb, wmcda->wNotifyDeviceID, MCI_NOTIFY_ABORTED);

1112 1113 1114 1115 1116 1117 1118 1119
    if (wmcda->hThread != 0) {
        /* Don't bother calling stop if the playLoop thread has already stopped */
        if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
           FAILED(IDirectSoundBuffer_Stop(wmcda->dsBuf)))
            return MCIERR_HARDWARE;
    }
    else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_PAUSE_AUDIO, NULL, 0, NULL, 0, &br, NULL))
        return MCIERR_HARDWARE;
1120

1121 1122
    if ((dwFlags & MCI_NOTIFY) && lpParms)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1123
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
1124 1125 1126
}

/**************************************************************************
1127
 * 				MCICDA_Resume			[internal]
1128
 */
1129
static DWORD MCICDA_Resume(UINT wDevID, DWORD dwFlags, LPMCI_GENERIC_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
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 1139 1140 1141 1142 1143 1144 1145
    if (wmcda->hThread != 0) {
        /* Don't restart if the playLoop thread has already stopped */
        if(WaitForSingleObject(wmcda->stopEvent, 0) != WAIT_OBJECT_0 &&
           FAILED(IDirectSoundBuffer_Play(wmcda->dsBuf, 0, 0, DSBPLAY_LOOPING)))
            return MCIERR_HARDWARE;
    }
    else if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_RESUME_AUDIO, NULL, 0, NULL, 0, &br, NULL))
        return MCIERR_HARDWARE;
1146

1147 1148
    if ((dwFlags & MCI_NOTIFY) && lpParms)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1149
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
1150 1151 1152
}

/**************************************************************************
1153
 * 				MCICDA_Seek			[internal]
1154
 */
1155
static DWORD MCICDA_Seek(UINT wDevID, DWORD dwFlags, LPMCI_SEEK_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
1156
{
1157 1158 1159
    DWORD		        at;
    WINE_MCICDAUDIO*	        wmcda = MCICDA_GetOpenDrv(wDevID);
    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 1169 1170 1171
    position = dwFlags & (MCI_SEEK_TO_START|MCI_SEEK_TO_END|MCI_TO);
    if (!position)		return MCIERR_MISSING_PARAMETER;
    if (position&(position-1))	return MCIERR_FLAGS_NOT_COMPATIBLE;

Jörg Höhle's avatar
Jörg Höhle committed
1172 1173 1174 1175 1176
    /* Stop sends MCI_NOTIFY_ABORTED when needed.
     * Tests show that native first sends ABORTED and reads the TOC,
     * then only checks the position flags, then stops and seeks. */
    MCICDA_Stop(wDevID, MCI_WAIT, 0);

1177 1178 1179 1180 1181
    if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_READ_TOC, NULL, 0,
                         &toc, sizeof(toc), &br, NULL)) {
        WARN("error reading TOC !\n");
        return MCICDA_GetError(wmcda);
    }
1182
    switch (position) {
1183
    case MCI_SEEK_TO_START:
1184
	TRACE("Seeking to start\n");
1185 1186 1187
	at = FRAME_OF_TOC(toc,toc.FirstTrack);
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
	  return ret;
1188 1189
	break;
    case MCI_SEEK_TO_END:
1190
	TRACE("Seeking to end\n");
Jörg Höhle's avatar
Jörg Höhle committed
1191 1192
	/* End is prior to lead-out
	 * yet Win9X seeks to even one frame less than that. */
1193
	at = FRAME_OF_TOC(toc, toc.LastTrack + 1) - 1;
1194 1195
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
	  return ret;
1196 1197
	break;
    case MCI_TO:
1198
	TRACE("Seeking to %u\n", lpParms->dwTo);
1199 1200 1201
        at = MCICDA_CalcFrame(wmcda, lpParms->dwTo);
	if ( (ret=MCICDA_SkipDataTracks(wmcda, &at)) )
	  return ret;
1202
	break;
1203
    default:
1204
	return MCIERR_FLAGS_NOT_COMPATIBLE;
1205
    }
1206

Jörg Höhle's avatar
Jörg Höhle committed
1207
    {
1208 1209 1210 1211 1212 1213 1214
        seek.M = at / CDFRAMES_PERMIN;
        seek.S = (at / CDFRAMES_PERSEC) % 60;
        seek.F = at % CDFRAMES_PERSEC;
        if (!DeviceIoControl(wmcda->handle, IOCTL_CDROM_SEEK_AUDIO_MSF, &seek, sizeof(seek),
                             NULL, 0, &br, NULL))
            return MCIERR_HARDWARE;
    }
1215

1216 1217
    if (dwFlags & MCI_NOTIFY)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1218
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
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 1228
    DWORD               br;

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 1235
                         (open) ? IOCTL_STORAGE_EJECT_MEDIA : IOCTL_STORAGE_LOAD_MEDIA,
                         NULL, 0, NULL, 0, &br, NULL))
1236
	return MCIERR_HARDWARE;
1237

1238 1239
    return 0;
}
Alexandre Julliard's avatar
Alexandre Julliard committed
1240 1241

/**************************************************************************
1242
 * 				MCICDA_Set			[internal]
1243
 */
1244
static DWORD MCICDA_Set(UINT wDevID, DWORD dwFlags, LPMCI_SET_PARMS lpParms)
Alexandre Julliard's avatar
Alexandre Julliard committed
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 1253 1254 1255 1256 1257 1258 1259

    if (dwFlags & MCI_SET_DOOR_OPEN) {
	MCICDA_SetDoor(wDevID, TRUE);
    }
    if (dwFlags & MCI_SET_DOOR_CLOSED) {
	MCICDA_SetDoor(wDevID, FALSE);
    }

    /* 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 1265 1266
    */
    if (dwFlags & MCI_SET_TIME_FORMAT) {
	switch (lpParms->dwTimeFormat) {
	case MCI_FORMAT_MILLISECONDS:
1267
	    TRACE("MCI_FORMAT_MILLISECONDS !\n");
1268 1269
	    break;
	case MCI_FORMAT_MSF:
1270
	    TRACE("MCI_FORMAT_MSF !\n");
1271 1272
	    break;
	case MCI_FORMAT_TMSF:
1273
	    TRACE("MCI_FORMAT_TMSF !\n");
1274 1275 1276 1277
	    break;
	default:
	    return MCIERR_BAD_TIME_FORMAT;
	}
1278
	wmcda->dwTimeFormat = lpParms->dwTimeFormat;
1279
    }
1280 1281
    if (dwFlags & MCI_SET_AUDIO) /* one xp machine ignored it */
	TRACE("SET_AUDIO %X %x\n", dwFlags, lpParms->dwAudio);
1282 1283 1284

    if (dwFlags & MCI_NOTIFY)
	MCICDA_Notify(lpParms->dwCallback, wmcda, MCI_NOTIFY_SUCCESSFUL);
1285
    return 0;
Alexandre Julliard's avatar
Alexandre Julliard committed
1286 1287 1288
}

/**************************************************************************
Patrik Stridvall's avatar
Patrik Stridvall committed
1289
 * 			DriverProc (MCICDA.@)
1290
 */
1291 1292
LRESULT CALLBACK MCICDA_DriverProc(DWORD_PTR dwDevID, HDRVR hDriv, UINT wMsg,
                                   LPARAM dwParam1, LPARAM dwParam2)
1293 1294 1295 1296
{
    switch(wMsg) {
    case DRV_LOAD:		return 1;
    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 1301
    case DRV_DISABLE:		return 1;
    case DRV_QUERYCONFIGURE:	return 1;
1302
    case DRV_CONFIGURE:		MessageBoxA(0, "MCI audio CD driver !", "Wine Driver", MB_OK); return 1;
1303 1304
    case DRV_INSTALL:		return DRVCNF_RESTART;
    case DRV_REMOVE:		return DRVCNF_RESTART;
1305
    }
1306

1307 1308 1309
    if (dwDevID == 0xFFFFFFFF) return MCIERR_UNSUPPORTED_FUNCTION;

    switch (wMsg) {
1310
    case MCI_OPEN_DRIVER:	return MCICDA_Open(dwDevID, dwParam1, (LPMCI_OPEN_PARMSW)dwParam2);
1311 1312
    case MCI_CLOSE_DRIVER:	return MCICDA_Close(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
    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 1315 1316 1317 1318 1319 1320
    case MCI_STATUS:		return MCICDA_Status(dwDevID, dwParam1, (LPMCI_STATUS_PARMS)dwParam2);
    case MCI_SET:		return MCICDA_Set(dwDevID, dwParam1, (LPMCI_SET_PARMS)dwParam2);
    case MCI_PLAY:		return MCICDA_Play(dwDevID, dwParam1, (LPMCI_PLAY_PARMS)dwParam2);
    case MCI_STOP:		return MCICDA_Stop(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
    case MCI_PAUSE:		return MCICDA_Pause(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
    case MCI_RESUME:		return MCICDA_Resume(dwDevID, dwParam1, (LPMCI_GENERIC_PARMS)dwParam2);
    case MCI_SEEK:		return MCICDA_Seek(dwDevID, dwParam1, (LPMCI_SEEK_PARMS)dwParam2);
1321 1322
    /* commands that should report an error as they are not supported in
     * the native version */
1323
    case MCI_RECORD:
1324 1325
    case MCI_LOAD:
    case MCI_SAVE:
1326 1327
	return MCIERR_UNSUPPORTED_FUNCTION;
    case MCI_BREAK:
1328 1329 1330 1331 1332 1333 1334 1335 1336 1337 1338 1339 1340 1341
    case MCI_FREEZE:
    case MCI_PUT:
    case MCI_REALIZE:
    case MCI_UNFREEZE:
    case MCI_UPDATE:
    case MCI_WHERE:
    case MCI_STEP:
    case MCI_SPIN:
    case MCI_ESCAPE:
    case MCI_COPY:
    case MCI_CUT:
    case MCI_DELETE:
    case MCI_PASTE:
    case MCI_WINDOW:
1342
	TRACE("Unsupported command [0x%x]\n", wMsg);
1343 1344 1345
	break;
    case MCI_OPEN:
    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;
Alexandre Julliard's avatar
Alexandre Julliard committed
1353 1354 1355
}

/*-----------------------------------------------------------------------*/