Always open/close the CD-ROM device for every Windows API CD-ROM
[wine] / misc / cdrom.c
1 /* -*- tab-width: 8; c-basic-offset: 4 -*- */
2 /*
3  * Main file for CD-ROM support
4  *
5  * Copyright 1994 Martin Ayotte
6  * Copyright 1999 Eric Pouech
7  * Copyright 2000 Andreas Mohr
8  */
9
10 #include "config.h"
11
12 #include <errno.h>
13 #include <string.h>
14 #include <fcntl.h>
15 #include <sys/ioctl.h>
16 #include "cdrom.h"
17 #include "drive.h"
18 #include "debugtools.h"
19 #include "winbase.h"
20 #include "wine/winestring.h"
21
22 DEFAULT_DEBUG_CHANNEL(cdrom);
23
24 #define MAX_CDAUDIO_TRACKS      256
25
26 #define CDROM_OPEN(wcda,parentdev) \
27     (((parentdev) == -1) ? CDROM_OpenDev(wcda) : (parentdev))
28
29 #define CDROM_CLOSE(dev,parentdev) \
30     (((parentdev) == -1) ? CDROM_CloseDev(dev) : 0)
31
32 /**************************************************************************
33  *                              CDROM_Open                      [internal]
34  *
35  * drive = 0, 1, ...
36  *      or -1 (figure it out)
37  */
38 int     CDROM_Open(WINE_CDAUDIO* wcda, int drive)
39 {
40     int i, dev;
41     BOOL avail = FALSE;
42
43     if (drive == -1)
44     {
45         for (i=0; i < MAX_DOS_DRIVES; i++)
46             if (DRIVE_GetType(i) == TYPE_CDROM)
47             {
48                 drive = i;
49                 avail = TRUE;
50                 break;
51             }
52     }
53     else
54         avail = TRUE;
55     
56     if (avail == FALSE)
57     {
58         WARN("No CD-ROM #%d found !\n", drive);
59         return -1;
60     }
61     if ((wcda->devname = DRIVE_GetDevice(drive)) == NULL)
62 {
63         WARN("No device entry for CD-ROM #%d (drive %c:) found !\n",
64                 drive, 'A' + drive);
65         return -1;
66     }
67
68     /* Test whether device can be opened */
69     dev = CDROM_OpenDev(wcda);
70     if (dev == -1)
71         return -1;
72     else
73         CDROM_CloseDev(dev);
74
75     wcda->cdaMode = WINE_CDA_OPEN;      /* to force reading tracks info */
76     wcda->nCurTrack = 0;
77     wcda->nTracks = 0;
78     wcda->dwFirstFrame = 0;
79     wcda->dwLastFrame = 0;
80     wcda->lpdwTrackLen = NULL;
81     wcda->lpdwTrackPos = NULL;
82     wcda->lpbTrackFlags = NULL;
83     TRACE("opened drive %c: (device %s)\n", 'A' + drive, wcda->devname);
84     return 0;
85 }
86
87 /**************************************************************************
88  *                              CDROM_OpenDev                   [internal]
89  *
90  */
91 int     CDROM_OpenDev(WINE_CDAUDIO* wcda)
92 {
93     int dev = open(wcda->devname, O_RDONLY | O_NONBLOCK, 0);
94     if (dev == -1)
95         WARN("can't open device '%s'! (%s)\n", wcda->devname, strerror(errno));
96
97     TRACE("-> %d\n", dev);
98     return dev;
99 }
100
101 /**************************************************************************
102  *                              CDROM_GetMediaType              [internal]
103  */
104 int     CDROM_GetMediaType(WINE_CDAUDIO* wcda, int parentdev)
105 {
106     int type = -1;
107 #ifdef linux
108     int dev = CDROM_OPEN( wcda, parentdev );
109     type = ioctl(dev, CDROM_DISC_STATUS);
110     CDROM_CLOSE( dev, parentdev );
111 #endif
112     TRACE("-> %d\n", type);
113     return type;
114 }
115
116 /**************************************************************************
117  *                              CDROM_Close                     [internal]
118  */
119 int     CDROM_CloseDev(int dev)
120 {
121     TRACE("%d\n", dev);
122     return close(dev);
123 }
124
125 /**************************************************************************
126  *                              CDROM_Close                     [internal]
127  */
128 int     CDROM_Close(WINE_CDAUDIO* wcda)
129 {
130 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
131     if (wcda->lpdwTrackLen != NULL) free(wcda->lpdwTrackLen);
132     if (wcda->lpdwTrackPos != NULL) free(wcda->lpdwTrackPos);
133     if (wcda->lpbTrackFlags != NULL) free(wcda->lpbTrackFlags);
134     TRACE("%s\n", wcda->devname);
135     return 0;
136 #else
137     return -1;
138 #endif
139 }
140
141 /**************************************************************************
142  *                              CDROM_Get_UPC                   [internal]
143  *
144  * upc has to be 14 bytes long
145  */
146 int CDROM_Get_UPC(WINE_CDAUDIO* wcda, LPSTR upc, int parentdev)
147 {
148 #ifdef linux
149     struct cdrom_mcn mcn;
150     int dev = CDROM_OPEN( wcda, parentdev );
151     int status = ioctl(dev, CDROM_GET_MCN, &mcn);
152     CDROM_CLOSE( dev, parentdev );
153     if (status)
154 {
155         ERR("ioctl() failed with code %d\n",status);
156         return -1;
157     }
158     strcpy(upc, mcn.medium_catalog_number);
159     return 0;
160 #else
161     return -1;
162 #endif
163 }
164
165 /**************************************************************************
166  *                              CDROM_Audio_GetNumberOfTracks   [internal]
167  */
168 UINT16 CDROM_Audio_GetNumberOfTracks(WINE_CDAUDIO* wcda, int parentdev)
169 {
170     UINT16 ret = (UINT16)-1;
171 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
172 #ifdef linux
173     struct cdrom_tochdr         hdr;
174 #else
175     struct ioc_toc_header       hdr;
176 #endif
177     int dev = CDROM_OPEN( wcda, parentdev );
178
179     if (wcda->nTracks == 0) {
180 #ifdef linux
181         if (ioctl(dev, CDROMREADTOCHDR, &hdr))
182 #else
183         if (ioctl(dev, CDIOREADTOCHEADER, &hdr))
184 #endif
185         {
186             WARN("(%p) -- Error occurred (%s)!\n", wcda, strerror(errno));
187             goto end;
188         }
189 #ifdef linux
190         wcda->nFirstTrack = hdr.cdth_trk0;
191         wcda->nLastTrack  = hdr.cdth_trk1;
192 #else   
193         wcda->nFirstTrack = hdr.starting_track;
194         wcda->nLastTrack  = hdr.ending_track;
195 #endif
196         wcda->nTracks = wcda->nLastTrack - wcda->nFirstTrack + 1;
197     }
198     ret = wcda->nTracks;
199 end:
200     CDROM_CLOSE( dev, parentdev );
201 #endif
202     return ret;
203 }
204
205 /**************************************************************************
206  *                      CDROM_Audio_GetTracksInfo               [internal]
207  */
208 BOOL CDROM_Audio_GetTracksInfo(WINE_CDAUDIO* wcda, int parentdev)
209 {
210     BOOL ret = FALSE;
211 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
212     int         i, length;
213     int         start, last_start = 0;
214     int         total_length = 0;
215 #ifdef linux
216     struct cdrom_tocentry       entry;
217 #else
218     struct ioc_read_toc_entry   entry;
219     struct cd_toc_entry         toc_buffer;
220 #endif
221     int dev = CDROM_OPEN( wcda, parentdev );
222     
223     if (wcda->nTracks == 0) {
224         if (CDROM_Audio_GetNumberOfTracks(wcda, dev) == (WORD)-1)
225         goto end;
226     }
227     TRACE("nTracks=%u\n", wcda->nTracks);
228     
229     if (wcda->lpdwTrackLen != NULL) 
230         free(wcda->lpdwTrackLen);
231     wcda->lpdwTrackLen = (LPDWORD)malloc((wcda->nTracks + 1) * sizeof(DWORD));
232     if (wcda->lpdwTrackPos != NULL) 
233         free(wcda->lpdwTrackPos);
234     wcda->lpdwTrackPos = (LPDWORD)malloc((wcda->nTracks + 1) * sizeof(DWORD));
235     if (wcda->lpbTrackFlags != NULL)
236         free(wcda->lpbTrackFlags);
237     wcda->lpbTrackFlags = (LPBYTE)malloc((wcda->nTracks + 1) * sizeof(BYTE));
238     if (wcda->lpdwTrackLen == NULL || wcda->lpdwTrackPos == NULL ||
239         wcda->lpbTrackFlags == NULL) {
240         WARN("error allocating track table !\n");
241         goto end;
242     }
243     memset(wcda->lpdwTrackLen, 0, (wcda->nTracks + 1) * sizeof(DWORD));
244     memset(wcda->lpdwTrackPos, 0, (wcda->nTracks + 1) * sizeof(DWORD));
245     memset(wcda->lpbTrackFlags, 0, (wcda->nTracks + 1) * sizeof(BYTE));
246     for (i = 0; i <= wcda->nTracks; i++) {
247         if (i == wcda->nTracks)
248 #ifdef linux
249             entry.cdte_track = CDROM_LEADOUT;
250 #else
251 #define LEADOUT 0xaa
252         entry.starting_track = LEADOUT; /* FIXME */
253 #endif
254         else
255 #ifdef linux
256         entry.cdte_track = i + 1;
257 #else
258         entry.starting_track = i + 1;
259 #endif
260 #ifdef linux
261         entry.cdte_format = CDROM_MSF;
262 #else
263         bzero((char *)&toc_buffer, sizeof(toc_buffer));
264         entry.address_format = CD_MSF_FORMAT;
265         entry.data_len = sizeof(toc_buffer);
266         entry.data = &toc_buffer;
267 #endif
268 #ifdef linux
269         if (ioctl(dev, CDROMREADTOCENTRY, &entry))
270 #else
271         if (ioctl(dev, CDIOREADTOCENTRYS, &entry))
272 #endif
273         {
274             WARN("error read entry (%s)\n", strerror(errno));
275             /* update status according to new status */
276             CDROM_Audio_GetCDStatus(wcda, dev);
277
278             goto end;
279         }
280 #ifdef linux
281         start = CDFRAMES_PERSEC * (SECONDS_PERMIN * 
282                                    entry.cdte_addr.msf.minute + entry.cdte_addr.msf.second) + 
283             entry.cdte_addr.msf.frame;
284 #else
285         start = CDFRAMES_PERSEC * (SECONDS_PERMIN *
286                                    toc_buffer.addr.msf.minute + toc_buffer.addr.msf.second) +
287             toc_buffer.addr.msf.frame;
288 #endif
289         if (i == 0) {
290             last_start = start;
291             wcda->dwFirstFrame = start;
292             TRACE("dwFirstOffset=%u\n", start);
293         } else {
294             length = start - last_start;
295             last_start = start;
296             start = last_start - length;
297             total_length += length;
298             wcda->lpdwTrackLen[i - 1] = length;
299             wcda->lpdwTrackPos[i - 1] = start;
300             TRACE("track #%u start=%u len=%u\n", i, start, length);
301         }
302 #ifdef linux
303         wcda->lpbTrackFlags[i] =
304             (entry.cdte_adr << 4) | (entry.cdte_ctrl & 0x0f);
305 #else
306         wcda->lpbTrackFlags[i] =
307             (toc_buffer.addr_type << 4) | (toc_buffer.control & 0x0f);
308 #endif 
309         TRACE("track #%u flags=%02x\n", i + 1, wcda->lpbTrackFlags[i]);
310     }
311     wcda->dwLastFrame = last_start;
312     TRACE("total_len=%u\n", total_length);
313     ret = TRUE;
314 end:
315     CDROM_CLOSE( dev, parentdev );
316 #endif
317     return ret;
318 }
319
320 /**************************************************************************
321  *                              CDROM_Audio_GetCDStatus         [internal]
322  */
323 BOOL CDROM_Audio_GetCDStatus(WINE_CDAUDIO* wcda, int parentdev)
324 {
325 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
326     int oldmode = wcda->cdaMode;
327     int ret = FALSE;
328     int dev = CDROM_OPEN( wcda, parentdev );
329 #ifdef linux
330     wcda->sc.cdsc_format = CDROM_MSF;
331 #else
332     struct ioc_read_subchannel  read_sc;
333     
334     read_sc.address_format = CD_MSF_FORMAT;
335     read_sc.data_format    = CD_CURRENT_POSITION;
336     read_sc.track          = 0;
337     read_sc.data_len       = sizeof(wcda->sc);
338     read_sc.data           = (struct cd_sub_channel_info *)&wcda->sc;
339 #endif
340 #ifdef linux
341     if (ioctl(dev, CDROMSUBCHNL, &wcda->sc))
342 #else
343     if (ioctl(dev, CDIOCREADSUBCHANNEL, &read_sc))
344 #endif
345     {
346         TRACE("opened or no_media (%s)!\n", strerror(errno));
347         wcda->cdaMode = WINE_CDA_OPEN; /* was NOT_READY */
348         goto end;
349     }
350     switch (
351 #ifdef linux
352             wcda->sc.cdsc_audiostatus
353 #else
354             wcda->sc.header.audio_status
355 #endif
356             ) {
357 #ifdef linux
358     case CDROM_AUDIO_INVALID:
359 #else
360     case CD_AS_AUDIO_INVALID:
361 #endif
362         WARN("device doesn't support status.\n");
363         wcda->cdaMode = WINE_CDA_DONTKNOW;
364         break;
365 #ifdef linux
366     case CDROM_AUDIO_NO_STATUS: 
367 #else
368     case CD_AS_NO_STATUS:
369 #endif
370         wcda->cdaMode = WINE_CDA_STOP;
371         TRACE("WINE_CDA_STOP !\n");
372         break;
373 #ifdef linux
374     case CDROM_AUDIO_PLAY: 
375 #else
376     case CD_AS_PLAY_IN_PROGRESS:
377 #endif
378         wcda->cdaMode = WINE_CDA_PLAY;
379         break;
380 #ifdef linux
381     case CDROM_AUDIO_PAUSED:
382 #else
383     case CD_AS_PLAY_PAUSED:
384 #endif
385         wcda->cdaMode = WINE_CDA_PAUSE;
386         TRACE("WINE_CDA_PAUSE !\n");
387         break;
388     default:
389 #ifdef linux
390         TRACE("status=%02X !\n",
391               wcda->sc.cdsc_audiostatus);
392 #else
393         TRACE("status=%02X !\n",
394               wcda->sc.header.audio_status);
395 #endif
396     }
397 #ifdef linux
398     wcda->nCurTrack = wcda->sc.cdsc_trk;
399     wcda->dwCurFrame = 
400         CDFRAMES_PERMIN * wcda->sc.cdsc_absaddr.msf.minute +
401         CDFRAMES_PERSEC * wcda->sc.cdsc_absaddr.msf.second +
402         wcda->sc.cdsc_absaddr.msf.frame;
403 #else
404     wcda->nCurTrack = wcda->sc.what.position.track_number;
405     wcda->dwCurFrame = 
406         CDFRAMES_PERMIN * wcda->sc.what.position.absaddr.msf.minute +
407         CDFRAMES_PERSEC * wcda->sc.what.position.absaddr.msf.second +
408         wcda->sc.what.position.absaddr.msf.frame;
409 #endif
410 #ifdef linux
411     TRACE("%02u-%02u:%02u:%02u\n",
412           wcda->sc.cdsc_trk,
413           wcda->sc.cdsc_absaddr.msf.minute,
414           wcda->sc.cdsc_absaddr.msf.second,
415           wcda->sc.cdsc_absaddr.msf.frame);
416 #else
417     TRACE("%02u-%02u:%02u:%02u\n",
418           wcda->sc.what.position.track_number,
419           wcda->sc.what.position.absaddr.msf.minute,
420           wcda->sc.what.position.absaddr.msf.second,
421           wcda->sc.what.position.absaddr.msf.frame);
422 #endif
423     
424     if (oldmode != wcda->cdaMode && oldmode == WINE_CDA_OPEN) {
425         if (!CDROM_Audio_GetTracksInfo(wcda, dev)) {
426             WARN("error updating TracksInfo !\n");
427             goto end;
428         }
429     }
430     if (wcda->cdaMode != WINE_CDA_OPEN)
431         ret = TRUE;
432 end:
433     CDROM_CLOSE( dev, parentdev );
434     return ret;
435 #else
436     return FALSE;
437 #endif
438 }
439
440 /**************************************************************************
441  *                              CDROM_Audio_Play                [internal]
442  */
443 int     CDROM_Audio_Play(WINE_CDAUDIO* wcda, DWORD start, DWORD end, int parentdev)
444 {
445     int ret = -1;
446 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
447 #ifdef linux
448     struct      cdrom_msf       msf;
449 #else
450     struct      ioc_play_msf    msf;
451 #endif
452     int dev = CDROM_OPEN( wcda, parentdev );
453
454 #ifdef linux
455     msf.cdmsf_min0 = start / CDFRAMES_PERMIN;
456     msf.cdmsf_sec0 = (start % CDFRAMES_PERMIN) / CDFRAMES_PERSEC;
457     msf.cdmsf_frame0 = start % CDFRAMES_PERSEC;
458     msf.cdmsf_min1 = end / CDFRAMES_PERMIN;
459     msf.cdmsf_sec1 = (end % CDFRAMES_PERMIN) / CDFRAMES_PERSEC;
460     msf.cdmsf_frame1 = end % CDFRAMES_PERSEC;
461 #else
462     msf.start_m     = start / CDFRAMES_PERMIN;
463     msf.start_s     = (start % CDFRAMES_PERMIN) / CDFRAMES_PERSEC;
464     msf.start_f     = start % CDFRAMES_PERSEC;
465     msf.end_m       = end / CDFRAMES_PERMIN;
466     msf.end_s       = (end % CDFRAMES_PERMIN) / CDFRAMES_PERSEC;
467     msf.end_f       = end % CDFRAMES_PERSEC;
468 #endif
469 #ifdef linux
470     if (ioctl(dev, CDROMSTART))
471 #else
472     if (ioctl(dev, CDIOCSTART, NULL))
473 #endif
474     {
475         WARN("motor doesn't start !\n");
476         goto end;
477     }
478 #ifdef linux
479     if (ioctl(dev, CDROMPLAYMSF, &msf))
480 #else
481     if (ioctl(dev, CDIOCPLAYMSF, &msf))
482 #endif
483     {
484         WARN("device doesn't play !\n");
485         goto end;
486     }
487 #ifdef linux
488     TRACE("msf = %d:%d:%d %d:%d:%d\n",
489           msf.cdmsf_min0, msf.cdmsf_sec0, msf.cdmsf_frame0,
490           msf.cdmsf_min1, msf.cdmsf_sec1, msf.cdmsf_frame1);
491 #else
492     TRACE("msf = %d:%d:%d %d:%d:%d\n",
493           msf.start_m, msf.start_s, msf.start_f,
494           msf.end_m,   msf.end_s,   msf.end_f);
495 #endif
496     ret = 0;
497 end:
498     CDROM_CLOSE( dev, parentdev );
499 #endif
500     return ret;
501 }
502
503 /**************************************************************************
504  *                              CDROM_Audio_Stop                [internal]
505  */
506 int     CDROM_Audio_Stop(WINE_CDAUDIO* wcda, int parentdev)
507 {
508     int ret = -1;
509 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
510     int dev = CDROM_OPEN( wcda, parentdev );
511 #ifdef linux
512     ret = ioctl(dev, CDROMSTOP);
513 #else
514     ret = ioctl(dev, CDIOCSTOP, NULL);
515 #endif
516     CDROM_CLOSE( dev, parentdev );
517 #endif
518     return ret;
519 }
520
521 /**************************************************************************
522  *                              CDROM_Audio_Pause               [internal]
523  */
524 int     CDROM_Audio_Pause(WINE_CDAUDIO* wcda, int pauseOn, int parentdev)
525 {
526     int ret = -1;
527 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
528     int dev = CDROM_OPEN( wcda, parentdev );
529 #ifdef linux
530     ret = ioctl(dev, pauseOn ? CDROMPAUSE : CDROMRESUME);
531 #else
532     ret = ioctl(dev, pauseOn ? CDIOCPAUSE : CDIOCRESUME, NULL);
533 #endif
534     CDROM_CLOSE( dev, parentdev );
535 #endif
536     return ret;
537 }
538
539 /**************************************************************************
540  *                              CDROM_Audio_Seek                [internal]
541  */
542 int     CDROM_Audio_Seek(WINE_CDAUDIO* wcda, DWORD at, int parentdev)
543 {
544     int ret = -1;
545 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
546     int dev = CDROM_OPEN( wcda, parentdev );
547 #ifdef linux
548     struct cdrom_msf0           msf;
549     msf.minute = at / CDFRAMES_PERMIN;
550     msf.second = (at % CDFRAMES_PERMIN) / CDFRAMES_PERSEC;
551     msf.frame  = at % CDFRAMES_PERSEC;
552
553     ret = ioctl(dev, CDROMSEEK, &msf);
554 #else
555    /* FIXME: the current end for play is lost 
556     * use end of CD ROM instead
557     */
558    FIXME("Could a BSD expert implement the seek function ?\n");
559    CDROM_Audio_Play(wcda, at, wcda->lpdwTrackPos[wcda->nTracks] + wcda->lpdwTrackLen[wcda->nTracks], dev);
560 #endif
561     CDROM_CLOSE( dev, parentdev );
562 #endif
563     return ret;
564 }
565
566 /**************************************************************************
567  *                              CDROM_SetDoor                   [internal]
568  */
569 int     CDROM_SetDoor(WINE_CDAUDIO* wcda, int open, int parentdev)
570 {
571     int ret = -1;
572 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
573     int dev = CDROM_OPEN( wcda, parentdev );
574
575     TRACE("%d\n", open);
576 #ifdef linux
577     if (open) {
578         ret = ioctl(dev, CDROMEJECT);
579     } else {
580         ret = ioctl(dev, CDROMEJECT, 1);
581     }
582 #else
583     ret = (ioctl(dev, CDIOCALLOW, NULL)) || 
584         (ioctl(dev, open ? CDIOCEJECT : CDIOCCLOSE, NULL)) ||
585         (ioctl(dev, CDIOCPREVENT, NULL));
586 #endif
587     wcda->nTracks = 0;
588     if (ret == -1)
589         WARN("failed (%s)\n", strerror(errno));
590     CDROM_CLOSE( dev, parentdev );
591 #endif
592     return ret;
593 }
594
595 /**************************************************************************
596  *                              CDROM_Reset                     [internal]
597  */
598 int     CDROM_Reset(WINE_CDAUDIO* wcda, int parentdev)
599 {
600     int ret = -1;
601 #if defined(linux) || defined(__FreeBSD__) || defined(__NetBSD__)
602     int dev = CDROM_OPEN( wcda, parentdev );
603 #ifdef linux
604     ret = ioctl(dev, CDROMRESET);
605 #else
606     ret = ioctl(dev, CDIOCRESET, NULL);
607 #endif
608     CDROM_CLOSE( dev, parentdev );
609 #endif
610     return ret;
611 }
612
613 WORD CDROM_Data_FindBestVoldesc(int fd)
614 {
615     BYTE cur_vd_type, max_vd_type = 0;
616     unsigned int offs, best_offs = 0;
617
618     for (offs=0x8000; offs <= 0x9800; offs += 0x800)
619     {
620         lseek(fd, offs, SEEK_SET);
621         read(fd, &cur_vd_type, 1);
622         if (cur_vd_type == 0xff) /* voldesc set terminator */
623             break;
624         if (cur_vd_type > max_vd_type)
625         {
626             max_vd_type = cur_vd_type;
627             best_offs = offs;
628         }
629     }
630     return best_offs;
631 }
632
633 /**************************************************************************
634  *                              CDROM_Audio_GetSerial           [internal]
635  */
636 DWORD CDROM_Audio_GetSerial(WINE_CDAUDIO* wcda)
637 {
638     unsigned long serial = 0;
639     int i;
640     DWORD dwFrame, msf;
641     WORD wMinutes, wSeconds, wFrames;
642         WORD wMagic;
643         DWORD dwStart, dwEnd;
644
645         /*
646          * wMagic collects the wFrames from track 1
647          * dwStart, dwEnd collect the beginning and end of the disc respectively, in
648          * frames.
649          * There it is collected for correcting the serial when there are less than
650          * 3 tracks.
651          */
652         wMagic = 0;
653         dwStart = dwEnd = 0;
654
655     for (i = 0; i < wcda->nTracks; i++) {
656         dwFrame = wcda->lpdwTrackPos[i];
657         wMinutes = dwFrame / CDFRAMES_PERMIN;
658         wSeconds = (dwFrame - CDFRAMES_PERMIN * wMinutes) / CDFRAMES_PERSEC;
659         wFrames = dwFrame - CDFRAMES_PERMIN * wMinutes - CDFRAMES_PERSEC * wSeconds;
660         msf = CDROM_MAKE_MSF(wMinutes, wSeconds, wFrames);
661
662         serial += (CDROM_MSF_MINUTE(msf) << 16) +
663         (CDROM_MSF_SECOND(msf) << 8) +
664         (CDROM_MSF_FRAME(msf));
665
666         if (i==0)
667         {
668                 wMagic = wFrames;
669                 dwStart = dwFrame;
670         }
671         dwEnd = dwFrame + wcda->lpdwTrackLen[i];
672         
673         }
674
675         if (wcda->nTracks < 3)
676         {
677                 serial += wMagic + (dwEnd - dwStart);
678         }
679     return serial;
680 }
681
682 /**************************************************************************
683  *                              CDROM_Data_GetSerial            [internal]
684  */
685 DWORD CDROM_Data_GetSerial(WINE_CDAUDIO* wcda, int parentdev)
686 {
687     int dev = CDROM_OPEN( wcda, parentdev );
688     WORD offs = CDROM_Data_FindBestVoldesc(dev);
689     union {
690         unsigned long val;
691         unsigned char p[4];
692     } serial;
693     BYTE b0 = 0, b1 = 1, b2 = 2, b3 = 3;
694     
695     serial.val = 0;
696     if (offs)
697     {
698         BYTE buf[2048];
699         OSVERSIONINFOA ovi;
700         int i;
701
702         lseek(dev,offs,SEEK_SET);
703         read(dev,buf,2048);
704         /*
705          * OK, another braindead one... argh. Just believe it.
706          * Me$$ysoft chose to reverse the serial number in NT4/W2K.
707          * It's true and nobody will ever be able to change it.
708          */
709         ovi.dwOSVersionInfoSize = sizeof(OSVERSIONINFOA);
710         GetVersionExA(&ovi);
711         if ((ovi.dwPlatformId == VER_PLATFORM_WIN32_NT)
712         &&  (ovi.dwMajorVersion >= 4))
713         {
714             b0 = 3; b1 = 2; b2 = 1; b3 = 0;
715         }
716         for(i=0; i<2048; i+=4)
717         {
718             /* DON'T optimize this into DWORD !! (breaks overflow) */
719             serial.p[b0] += buf[i+b0];
720             serial.p[b1] += buf[i+b1];
721             serial.p[b2] += buf[i+b2];
722             serial.p[b3] += buf[i+b3];
723         }
724     }
725     CDROM_CLOSE( dev, parentdev );
726     return serial.val;
727 }
728
729 /**************************************************************************
730  *                              CDROM_GetSerial                 [internal]
731  */
732 DWORD CDROM_GetSerial(int drive)
733 {
734     WINE_CDAUDIO wcda;
735     DWORD serial = 0;
736
737     /* EXPIRES 01.01.2002 */
738     WARN("CD-ROM serial number calculation might fail.\n");
739     WARN("Please test with as many exotic CDs as possible !\n");
740
741     if (!(CDROM_Open(&wcda, drive)))
742     {
743         int dev = CDROM_OpenDev(&wcda);
744         int media = CDROM_GetMediaType(&wcda, dev);
745
746         switch (media)
747         {
748             case CDS_AUDIO:
749             case CDS_MIXED: /* mixed is basically a mountable audio CD */
750                 if (!(CDROM_Audio_GetCDStatus(&wcda, dev))) {
751                     ERR("couldn't get CD status !\n");
752                     goto end;
753                 }
754                 serial = CDROM_Audio_GetSerial(&wcda);
755                 break;
756             case CDS_DATA_1:
757             case CDS_DATA_2:
758             case CDS_XA_2_1:
759             case CDS_XA_2_2:
760             case -1: /* ioctl() error: ISO9660 image file given ? */
761                 /* hopefully a data CD */
762                 serial = CDROM_Data_GetSerial(&wcda, dev);
763                 break;
764             default:
765                 WARN("Strange CD type (%d) or empty ?\n", media);
766         }
767         if (serial)
768             TRACE("CD serial number is %04x-%04x.\n",
769                 HIWORD(serial), LOWORD(serial));
770         else
771             if (media >= CDS_AUDIO)
772                 ERR("couldn't get CD serial !\n");
773 end:
774         CDROM_CloseDev(dev);
775         CDROM_Close(&wcda);
776     }
777     return serial;
778 }
779
780 static const char empty_label[] = "           ";
781
782 /**************************************************************************
783  *                              CDROM_Data_GetLabel             [internal]
784  */
785 DWORD CDROM_Data_GetLabel(WINE_CDAUDIO* wcda, char *label, int parentdev)
786 {
787 #define LABEL_LEN       32+1
788     int dev = CDROM_OPEN( wcda, parentdev );
789     WORD offs = CDROM_Data_FindBestVoldesc(dev);
790     WCHAR label_read[LABEL_LEN]; /* Unicode possible, too */
791     DWORD unicode_id = 0;
792
793     if (offs)
794     {
795         if ((lseek(dev, offs+0x58, SEEK_SET) == offs+0x58)
796         &&  (read(dev, &unicode_id, 3) == 3))
797         {
798             int ver = (unicode_id & 0xff0000) >> 16;
799
800             if ((lseek(dev, offs+0x28, SEEK_SET) != offs+0x28)
801             ||  (read(dev, &label_read, LABEL_LEN) != LABEL_LEN))
802                 goto failure;
803
804             CDROM_CLOSE( dev, parentdev );
805             if ((LOWORD(unicode_id) == 0x2f25) /* Unicode ID */
806             &&  ((ver == 0x40) || (ver == 0x43) || (ver == 0x45)))
807             { /* yippee, unicode */
808                 int i;
809                 WORD ch;
810                 for (i=0; i<LABEL_LEN;i++)
811                 { /* Motorola -> Intel Unicode conversion :-\ */
812                      ch = label_read[i];
813                      label_read[i] = (ch << 8) | (ch >> 8);
814                 }
815                 lstrcpynWtoA(label, label_read, 11);
816             }
817             else
818             {
819                 strncpy(label, (LPSTR)label_read, 11);
820                 label[11] = '\0';
821             }
822             return 0;
823         }
824     }
825 failure:
826     CDROM_CLOSE( dev, parentdev );
827     ERR("error reading label !\n");
828     strcpy(label, empty_label);
829     return 0;
830 }
831
832 /**************************************************************************
833  *                              CDROM_GetLabel                  [internal]
834  */
835 DWORD CDROM_GetLabel(int drive, char *label)
836 {
837     WINE_CDAUDIO wcda;
838     DWORD ret = 1;
839
840     if (!(CDROM_Open(&wcda, drive)))
841     {
842         int dev = CDROM_OpenDev(&wcda);
843         int media = CDROM_GetMediaType(&wcda, dev);
844         LPSTR cdname = NULL;
845
846         switch (media)
847         {
848             case CDS_AUDIO:
849                 cdname = "Audio";
850                 strcpy(label, "Audio CD   ");
851                 break;
852
853             case CDS_DATA_1: /* fall through for all data CD types !! */
854                 if (!cdname) cdname = "Data_1";
855             case CDS_DATA_2:
856                 if (!cdname) cdname = "Data_2";
857             case CDS_XA_2_1:
858                 if (!cdname) cdname = "XA 2.1";
859             case CDS_XA_2_2:
860                 if (!cdname) cdname = "XA 2.2";
861             case -1:
862                 if (!cdname) cdname = "Unknown/ISO file";
863
864                 /* common code *here* !! */
865                 /* hopefully a data CD */
866                 CDROM_Data_GetLabel(&wcda, label, dev);
867                 break;
868
869             case CDS_MIXED:
870                 cdname = "Mixed mode";
871                 ERR("We don't have a way of determining the label of a mixed mode CD - Linux doesn't allow raw access !!\n");
872                 /* fall through */
873             case CDS_NO_INFO:
874                 if (!cdname) cdname = "No_info";
875                 strcpy(label, empty_label);
876                 break;
877
878             default:
879                 WARN("Strange CD type (%d) or empty ?\n", media);
880                 cdname = "Strange/empty";
881                 strcpy(label, empty_label);
882                 ret = 0;
883                 break;
884         }
885         
886         CDROM_CloseDev(dev);
887         CDROM_Close(&wcda);
888         TRACE("%s CD: label is '%s'.\n",
889             cdname, label);
890     }
891     else
892         ret = 0;
893
894     return ret;
895 }
896