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