1 //==================================================================//
2 /*
3     AtomicParsley - CDtoc.cpp
4 
5     AtomicParsley is GPL software; you can freely distribute,
6     redistribute, modify & use under the terms of the GNU General
7     Public License; either version 2 or its successor.
8 
9     AtomicParsley is distributed under the GPL "AS IS", without
10     any warranty; without the implied warranty of merchantability
11     or fitness for either an expressed or implied particular purpose.
12 
13     Please see the included GNU General Public License (GPL) for
14     your rights and further details; see the file COPYING. If you
15     cannot, write to the Free Software Foundation, 59 Temple Place
16     Suite 330, Boston, MA 02111-1307, USA.  Or www.fsf.org
17 
18     Copyright (C)2006-2007 puck_lock
19     with contributions from others; see the CREDITS file
20                                                                    */
21 //==================================================================//
22 
23 // gathering of a CD's Table of Contents is going to be hardware specific
24 // currently only Mac OS X is implemented - using IOKit framework.
25 // another avenue (applicable to other *nix platforms): ioctl
26 
27 #include "AtomicParsley.h"
28 #include "CDtoc.h"
29 
30 #if defined(__APPLE__)
31 #include <CoreFoundation/CoreFoundation.h>
32 #include <IOKit/IOBSD.h>
33 #include <IOKit/IOKitLib.h>
34 #include <IOKit/storage/IOCDMedia.h>
35 #include <IOKit/storage/IOCDTypes.h>
36 
37 const uint8_t MACOSX_LEADOUT_TRACK = 0xA2;
38 #endif
39 
40 const uint8_t CDOBJECT_DATACD = 0;
41 const uint8_t CDOBJECT_AUDIOCD = 1;
42 
43 struct CD_TDesc {
44   uint8_t session;
45   uint8_t controladdress;
46   uint8_t unused1;     // refered to as 'tno' which equates to "track number" -
47                        // but... its 'point' that actually is the tracknumber in
48                        // mode1 TOC. set to zero for all mode1 TOC
49   uint8_t tracknumber; // refered to as 'point', but this is actually the
50                        // tracknumber in mode1 audio toc entries.
51   uint8_t rel_minutes;
52   uint8_t rel_seconds;
53   uint8_t rel_frames;
54   uint8_t zero_space;
55   uint8_t abs_minutes;
56   uint8_t abs_seconds;
57   uint8_t abs_frames;
58   void *next_description;
59 };
60 typedef struct CD_TDesc CD_TDesc;
61 
62 struct CD_TOC_ {
63   uint16_t toc_length;
64   uint8_t first_session;
65   uint8_t last_session;
66   CD_TDesc *track_description; // entry to the first track in the linked list
67 };
68 typedef struct CD_TOC_ CD_TOC_;
69 
70 CD_TOC_ *cdTOC = NULL;
71 
72 #if defined(__APPLE__)
73 uint8_t LEADOUT_TRACK_NUMBER = MACOSX_LEADOUT_TRACK;
74 #elif defined(__linux__)
75 uint8_t LEADOUT_TRACK_NUMBER = CDROM_LEADOUT;
76 #else
77 uint8_t LEADOUT_TRACK_NUMBER =
78     0xAA; // NOTE: for WinXP IOCTL_CDROM_READ_TOC_EX code, its 0xA2
79 #endif
80 
81 /*
82 MCDI describes the CD TOC - actually talks about "a binary dump of the TOC".
83 So, a TOC is made up of: a 4 byte TOC header (2 bytes length of the entire TOC,
84 1 byte start session, 1 byte end session) an array of track entries, and
85 depending on the mode, of varying lengths. For audio CDs, TOC track entries are
86 mode1 (or for CD-R/RW mode5, but lets stick to mode1) a mode1 track entry is 11
87 bytes: 1byte session, 1 byte (packed control/address), 1byte NULL (unused TNO),
88 1 byte for tracknumber (expressed as the word POINT in mmc nomenclature),
89 3bytes relative start frametime, 1 byte NULL, 3 bytes duration timeframe
90 
91 while "binary dump of the TOC" is there, its also modified so that the
92 timeframe listing in mm:ss::frames (3bytes) is converted to a 4byte LBA
93 timecode.  Combining the first 4 bytes of the "binary dump of the TOC" with the
94 modifications of the 3byte(frame)->4byte(block), we arrive at MCDI as:
95 
96 struct mcdi_track_entry {
97   uint8_t   cd_toc_session;
98   uint8_t   cd_toc_controladdress; //bitpacked uint4_t of control & address
99   uint8_t   cd_toc_TNO = 0; //hardcoded to 0 for mode1 audio tracks in the TOC
100   uint8_t   cd_toc_tracknumber; //this is the 1-99 tracknumber (listed in mmc-2
101 as POINT) uint32_t  cd_frame_address; //converted from the 3byte mm:ss:frame
102 absolute duration
103 };
104 struct toc_header {
105         uint16_t  toc_length;
106         uin8_t    first_track;
107         uint8_t   last_track;
108 };
109 struct mcdi_frame {
110         struct toc_header;
111   struct mcdi_track_entry[total_audio_tracks];
112   struct mcdi_track_entry lead_out;
113   };
114 
115 The problem with including the TOC header is that it can't be used directly
116 because on the CD toc entries are 3byte msf address, but here they are 4byte
117 LBA. In any event this header should not have ever been included because the
118 length can be deduced from the frame length & tracks by dividing by 8. So, the
119 header length that MCDI refers to: is it for MSF or LBA addressing? Well, since
120 the rest of MCDI is LBA-based, lets say LBA - which means it needs to be
121 calculated. As it just so happens, then its the length of the frame. All that
122 needs to be added are the first & last tracks.
123 
124 Unfortunately, this frame can't be used as a CD Identifier *AS IS* across
125 platforms. Because the leadout track is platform specific (a Linux leadout is
126 0xAA, MacOSX leadout is 0xA2), consideration of the leadout track would have to
127 be given by anything else using this frame.
128 
129 */
130 
131 ///////////////////////////////////////////////////////////////////////////
132 //          Generating MCDI data from a CD TOC                           //
133 ///////////////////////////////////////////////////////////////////////////
134 
DataControlField(uint8_t controlfield)135 uint8_t DataControlField(uint8_t controlfield) {
136 #if defined(__ppc__) || defined(__ppc64__)
137   if (controlfield & 0x04) { // data uninterrupted or increment OR reserved;
138                              // this field is already bitpacked as controlfield
139     return 1;
140   }
141 #else
142   if (controlfield &
143       0x40) { // data uninterrupted or increment OR reserved; bitpacked already
144     return 1;
145   }
146 #endif
147   return 0;
148 }
149 
DetermineCDType(CD_TOC_ * cdTOCdata)150 uint8_t DetermineCDType(CD_TOC_ *cdTOCdata) {
151   CD_TDesc *track_TOC_desc = cdTOCdata->track_description;
152   while (track_TOC_desc != NULL) {
153     if (track_TOC_desc->tracknumber >= 1 && track_TOC_desc->tracknumber <= 99 &&
154         !DataControlField(track_TOC_desc->controladdress)) {
155       return CDOBJECT_AUDIOCD;
156     }
157     track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description;
158   }
159   return CDOBJECT_DATACD;
160 }
161 
LeadOutTrack(CD_TOC_ * cdTOCdata)162 CD_TDesc *LeadOutTrack(CD_TOC_ *cdTOCdata) {
163   CD_TDesc *track_TOC_desc = cdTOCdata->track_description;
164   while (track_TOC_desc != NULL) {
165     if (track_TOC_desc->tracknumber == LEADOUT_TRACK_NUMBER) {
166       return track_TOC_desc;
167     }
168     track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description;
169   }
170   return NULL;
171 }
172 
FillSingleMCDIentry(CD_TDesc * atrack,char * mcdi_data_entry)173 uint8_t FillSingleMCDIentry(CD_TDesc *atrack, char *mcdi_data_entry) {
174   mcdi_data_entry[0] = atrack->session;
175   mcdi_data_entry[1] = atrack->controladdress;
176   mcdi_data_entry[2] = 0;
177   mcdi_data_entry[3] = atrack->tracknumber;
178   // LBA=(M*60+S)*75+F - 150 (table 374)
179   uint32_t frameduration =
180       ((((atrack->abs_minutes * 60) + atrack->abs_seconds) * 75) +
181        atrack->abs_frames) -
182       150;
183   UInt32_TO_String4(frameduration, mcdi_data_entry + 4);
184   return 8;
185 }
186 
FormMCDIdata(char * mcdi_data)187 uint16_t FormMCDIdata(char *mcdi_data) {
188   uint16_t mcdi_len = 0;
189   uint8_t first_track = 0;
190   uint8_t last_track = 0;
191 
192   CD_TDesc *track_TOC_desc = cdTOC->track_description;
193 
194   if (cdTOC->track_description != NULL) {
195     mcdi_len += 4;
196 
197     while (track_TOC_desc != NULL) {
198       if (track_TOC_desc->tracknumber >= 1 &&
199           track_TOC_desc->tracknumber <= 99 &&
200           !DataControlField(track_TOC_desc->controladdress)) {
201         mcdi_len += FillSingleMCDIentry(track_TOC_desc, mcdi_data + mcdi_len);
202         if (first_track == 0) {
203           first_track = track_TOC_desc->tracknumber;
204         }
205         last_track = track_TOC_desc->tracknumber;
206       }
207       track_TOC_desc = (CD_TDesc *)track_TOC_desc->next_description;
208     }
209     if (mcdi_len > 0) {
210       CD_TDesc *leadout = LeadOutTrack(cdTOC);
211       if (leadout != NULL) {
212         mcdi_len += FillSingleMCDIentry(leadout, mcdi_data + mcdi_len);
213       }
214     }
215     // backtrack & fill in the header
216     UInt16_TO_String2(mcdi_len, mcdi_data);
217     mcdi_data[2] = first_track;
218     mcdi_data[3] = last_track;
219   }
220   return mcdi_len;
221 }
222 
223 /////////////////////////////////////////////////////////////////////////////
224 //                      Platform Specifics                                 //
225 /////////////////////////////////////////////////////////////////////////////
226 
227 #if defined(__linux__)
Linux_ReadCDTOC(int cd_fd)228 void Linux_ReadCDTOC(int cd_fd) {
229   cdrom_tochdr toc_header;
230   cdrom_tocentry toc_entry;
231   CD_TDesc *a_TOC_desc = NULL;
232   CD_TDesc *prev_desc = NULL;
233 
234   if (ioctl(cd_fd, CDROMREADTOCHDR, &toc_header) == -1) {
235     fprintf(stderr,
236             "AtomicParsley error: there was an error reading the CD "
237             "Table of Contents header.\n");
238     return;
239   }
240   cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_));
241   cdTOC->track_description = NULL;
242 
243   for (uint8_t i = toc_header.cdth_trk0; i <= toc_header.cdth_trk1 + 1; i++) {
244     memset(&toc_entry, 0, sizeof(toc_entry));
245     if (i == toc_header.cdth_trk1 + 1) {
246       toc_entry.cdte_track = CDROM_LEADOUT;
247     } else {
248       toc_entry.cdte_track = i;
249     }
250     toc_entry.cdte_format =
251         CDROM_MSF; // although it could just be easier to use CDROM_LBA
252 
253     if (ioctl(cd_fd, CDROMREADTOCENTRY, &toc_entry) == -1) {
254       fprintf(stderr,
255               "AtomicParsley error: there was an error reading a "
256               "CD Table of Contents entry (linux cdrom).\n");
257       return;
258     }
259 
260     if (cdTOC->track_description == NULL) {
261       cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
262       a_TOC_desc = cdTOC->track_description;
263       prev_desc = a_TOC_desc;
264     } else {
265       prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
266       a_TOC_desc = (CD_TDesc *)prev_desc->next_description;
267       prev_desc = a_TOC_desc;
268     }
269 
270     a_TOC_desc->session = 1; // and for vanilla audio CDs it is session 1, but
271                              // for multi-session...
272 #if defined(__ppc__) || defined(__ppc64__)
273     a_TOC_desc->controladdress =
274         (toc_entry.cdte_ctrl << 4) | toc_entry.cdte_adr;
275 #else
276     a_TOC_desc->controladdress =
277         (toc_entry.cdte_adr << 4) | toc_entry.cdte_ctrl;
278 #endif
279     a_TOC_desc->unused1 = 0;
280     a_TOC_desc->tracknumber = toc_entry.cdte_track;
281     a_TOC_desc->rel_minutes =
282         0; // is there anyway to even find this out on
283            // linux without playing the track? //cdmsf_min0
284     a_TOC_desc->rel_seconds = 0;
285     a_TOC_desc->rel_frames = 0;
286     a_TOC_desc->zero_space = 0;
287     a_TOC_desc->abs_minutes = toc_entry.cdte_addr.msf.minute;
288     a_TOC_desc->abs_seconds = toc_entry.cdte_addr.msf.second;
289     a_TOC_desc->abs_frames = toc_entry.cdte_addr.msf.frame;
290   }
291   return;
292 }
293 
Linux_ioctlProbeTargetDrive(const char * id3args_drive,char * mcdi_data)294 uint16_t Linux_ioctlProbeTargetDrive(const char *id3args_drive,
295                                      char *mcdi_data) {
296   uint16_t mcdi_data_len = 0;
297   int cd_fd = 0;
298 
299   cd_fd = open(id3args_drive, O_RDONLY | O_NONBLOCK);
300 
301   if (cd_fd != -1) {
302     int cd_mode = ioctl(cd_fd, CDROM_DISC_STATUS);
303     if (cd_mode != CDS_AUDIO || cd_mode != CDS_MIXED) {
304       Linux_ReadCDTOC(cd_fd);
305       mcdi_data_len = FormMCDIdata(mcdi_data);
306     } else {
307       // scan for available devices
308     }
309   } else {
310     // scan for available devices
311   }
312 
313   return mcdi_data_len;
314 }
315 #endif
316 
317 #if defined(__APPLE__)
Extract_cdTOCrawdata(CFDataRef cdTOCdata,char * cdTOCrawdata)318 uint16_t Extract_cdTOCrawdata(CFDataRef cdTOCdata, char *cdTOCrawdata) {
319   CFRange cdrange;
320   CFIndex cdTOClen = CFDataGetLength(cdTOCdata);
321   cdTOCrawdata = (char *)calloc(1, sizeof(char) * cdTOClen + 1);
322   cdrange = CFRangeMake(0, cdTOClen + 1);
323   CFDataGetBytes(cdTOCdata, cdrange, (unsigned char *)cdTOCrawdata);
324 
325   cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_));
326   cdTOC->toc_length = UInt16FromBigEndian(cdTOCrawdata);
327   cdTOC->first_session = cdTOCrawdata[2];
328   cdTOC->first_session = cdTOCrawdata[3];
329   cdTOC->track_description = NULL;
330 
331   CD_TDesc *a_TOC_desc = NULL;
332   CD_TDesc *prev_desc = NULL;
333 
334   uint16_t toc_offset = 0;
335   for (toc_offset = 4; toc_offset <= cdTOClen; toc_offset += 11) {
336     if (cdTOC->track_description == NULL) {
337       cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
338       a_TOC_desc = cdTOC->track_description;
339       prev_desc = a_TOC_desc;
340     } else {
341       prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
342       a_TOC_desc = (CD_TDesc *)prev_desc->next_description;
343       prev_desc = a_TOC_desc;
344     }
345     a_TOC_desc->session = cdTOCrawdata[toc_offset];
346     a_TOC_desc->controladdress = cdTOCrawdata[toc_offset + 1];
347     a_TOC_desc->unused1 = cdTOCrawdata[toc_offset + 2];
348     a_TOC_desc->tracknumber = cdTOCrawdata[toc_offset + 3];
349     a_TOC_desc->rel_minutes = cdTOCrawdata[toc_offset + 4];
350     a_TOC_desc->rel_seconds = cdTOCrawdata[toc_offset + 5];
351     a_TOC_desc->rel_frames = cdTOCrawdata[toc_offset + 6];
352     a_TOC_desc->zero_space = 0;
353     a_TOC_desc->abs_minutes = cdTOCrawdata[toc_offset + 8];
354     a_TOC_desc->abs_seconds = cdTOCrawdata[toc_offset + 9];
355     a_TOC_desc->abs_frames = cdTOCrawdata[toc_offset + 10];
356   }
357 
358   return (uint16_t)cdTOClen;
359 }
360 
OSX_ReadCDTOC(io_object_t cdobject)361 void OSX_ReadCDTOC(io_object_t cdobject) {
362   CFMutableDictionaryRef cd_props = 0;
363   CFDataRef cdTOCdata = NULL;
364   char *cdTOCrawdata = NULL;
365 
366   if (IORegistryEntryCreateCFProperties(
367           cdobject, &cd_props, kCFAllocatorDefault, kNilOptions) !=
368       kIOReturnSuccess)
369     return;
370 
371   cdTOCdata =
372       (CFDataRef)CFDictionaryGetValue(cd_props, CFSTR(kIOCDMediaTOCKey));
373   if (cdTOCdata != NULL) {
374     Extract_cdTOCrawdata(cdTOCdata, cdTOCrawdata);
375   }
376   CFRelease(cd_props);
377   cd_props = NULL;
378   return;
379 }
380 
OSX_ScanForCDDrive()381 void OSX_ScanForCDDrive() {
382   io_iterator_t drive_iter = MACH_PORT_NULL;
383   io_object_t driveobject = MACH_PORT_NULL;
384   CFTypeRef drive_path = NULL;
385   char drive_path_str[20];
386   if (IOServiceGetMatchingServices(kIOMasterPortDefault,
387                                    IOServiceMatching(kIOCDMediaClass),
388                                    &drive_iter) !=
389       kIOReturnSuccess) { // create the iterator
390     fprintf(stdout, "No device capable of reading cd media present\n");
391   }
392 
393   driveobject = IOIteratorNext(drive_iter);
394   while (driveobject != MACH_PORT_NULL) {
395     drive_path = IORegistryEntryCreateCFProperty(
396         driveobject, CFSTR(kIOBSDNameKey), kCFAllocatorDefault, 0);
397     if (drive_path != NULL) {
398       CFStringGetCString((CFStringRef)drive_path,
399                          (char *)&drive_path_str,
400                          20,
401                          kCFStringEncodingASCII);
402       fprintf(stdout, "Device '%s' contains cd media\n", drive_path_str);
403       OSX_ReadCDTOC(driveobject);
404       if (cdTOC != NULL) {
405         uint8_t cdType = DetermineCDType(cdTOC);
406         if (cdType == CDOBJECT_AUDIOCD) {
407           fprintf(stdout,
408                   "Good news, device '%s' is an Audio CD and can be used for "
409                   "'MCDI' setting\n",
410                   drive_path_str);
411         } else {
412           fprintf(stdout, "Tragically, it was a data CD.\n");
413         }
414         free(cdTOC); // the other malloced members should be freed also
415       }
416     }
417     IOObjectRelease(driveobject);
418     driveobject = IOIteratorNext(drive_iter);
419   }
420 
421   if (drive_path_str[0] == (uint8_t)0x00) {
422     fprintf(stdout, "No CD media was found in any device\n");
423   }
424   IOObjectRelease(drive_iter);
425   drive_iter = MACH_PORT_NULL;
426   exit(0);
427 }
428 
OSX_ProbeTargetDrive(const char * id3args_drive,char * mcdi_data)429 uint16_t OSX_ProbeTargetDrive(const char *id3args_drive, char *mcdi_data) {
430   uint16_t mcdi_data_len = 0;
431   io_object_t cdobject = MACH_PORT_NULL;
432 
433   if (strncmp(id3args_drive, "disk", 4) != 0) {
434     OSX_ScanForCDDrive();
435     exit(0);
436   }
437   cdobject = IOServiceGetMatchingService(
438       kIOMasterPortDefault,
439       IOBSDNameMatching(kIOMasterPortDefault, 0, id3args_drive));
440 
441   if (cdobject == MACH_PORT_NULL) {
442     fprintf(stdout,
443             "No device found at %s; searching for possible drives...\n",
444             id3args_drive);
445     OSX_ScanForCDDrive();
446 
447   } else if (IOObjectConformsTo(cdobject, kIOCDMediaClass) == false) {
448     fprintf(stdout, "No cd present in drive at %s\n", id3args_drive);
449     IOObjectRelease(cdobject);
450     cdobject = MACH_PORT_NULL;
451     OSX_ScanForCDDrive();
452   } else {
453     // we now have a cd object
454     OSX_ReadCDTOC(cdobject);
455     if (cdTOC != NULL) {
456       uint8_t cdType = DetermineCDType(cdTOC);
457       if (cdType == CDOBJECT_AUDIOCD) {
458         mcdi_data_len = FormMCDIdata(mcdi_data);
459       }
460     }
461   }
462 
463   IOObjectRelease(cdobject);
464   cdobject = MACH_PORT_NULL;
465   return mcdi_data_len;
466 }
467 
468 #endif
469 
470 #if defined(_WIN32)
Windows_ioctlReadCDTOC(HANDLE cdrom_device)471 void Windows_ioctlReadCDTOC(HANDLE cdrom_device) {
472   DWORD bytes_returned;
473   CDROM_TOC win_cdrom_toc;
474 
475   // WARNING: "This IOCTL is obsolete beginning with the Microsoft Windows
476   // Vista. Do not use this IOCTL to develop drivers in Microsoft Windows
477   // Vista."
478   if (DeviceIoControl(cdrom_device,
479                       IOCTL_CDROM_READ_TOC,
480                       NULL,
481                       0,
482                       &win_cdrom_toc,
483                       sizeof(CDROM_TOC),
484                       &bytes_returned,
485                       NULL) == 0) {
486     fprintf(stderr,
487             "AtomicParsley error: there was an error reading the CD "
488             "Table of Contents header (win32).\n");
489     return;
490   }
491 
492   cdTOC = (CD_TOC_ *)calloc(1, sizeof(CD_TOC_));
493   cdTOC->toc_length = ((win_cdrom_toc.Length[0] & 0xFF) << 8) |
494                       (win_cdrom_toc.Length[1] & 0xFF);
495   // cdTOC->first_session = 0; //seems windows doesn't store session info with
496   // IOCTL_CDROM_READ_TOC, all tracks from all sessions are available
497   // cdTOC->last_session = 0; //not used anyway; IOCTL_CDROM_READ_TOC_EX could
498   // be used to get session information, but its only available on XP
499   //...............and interestingly enough in XP, IOCTL_CDROM_TOC_EX returns
500   // the leadout track as 0xA2, which makes a Win2k MCDI & a WinXP MCDI
501   // different (by 1 byte)
502   cdTOC->track_description = NULL;
503 
504   CD_TDesc *a_TOC_desc = NULL;
505   CD_TDesc *prev_desc = NULL;
506 
507   for (uint8_t i = win_cdrom_toc.FirstTrack; i <= win_cdrom_toc.LastTrack + 1;
508        i++) {
509     TRACK_DATA *thisTrackData = &(win_cdrom_toc.TrackData[i - 1]);
510 
511     if (cdTOC->track_description == NULL) {
512       cdTOC->track_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
513       a_TOC_desc = cdTOC->track_description;
514       prev_desc = a_TOC_desc;
515     } else {
516       prev_desc->next_description = (CD_TDesc *)calloc(1, (sizeof(CD_TDesc)));
517       a_TOC_desc = (CD_TDesc *)prev_desc->next_description;
518       prev_desc = a_TOC_desc;
519     }
520 
521     a_TOC_desc->session = 1; // and for vanilla audio CDs it is session 1, but
522                              // for multi-session...
523 #if defined(__ppc__) || defined(__ppc64__)
524     a_TOC_desc->controladdress =
525         (thisTrackData->Control << 4) | thisTrackData->Adr;
526 #else
527     a_TOC_desc->controladdress =
528         (thisTrackData->Adr << 4) | thisTrackData->Control;
529 #endif
530     a_TOC_desc->unused1 = 0;
531     a_TOC_desc->tracknumber = thisTrackData->TrackNumber;
532     a_TOC_desc->rel_minutes =
533         0; // didn't look too much into finding this since it is unused
534     a_TOC_desc->rel_seconds = 0;
535     a_TOC_desc->rel_frames = 0;
536     a_TOC_desc->zero_space = 0;
537     a_TOC_desc->abs_minutes = thisTrackData->Address[1];
538     a_TOC_desc->abs_seconds = thisTrackData->Address[2];
539     a_TOC_desc->abs_frames = thisTrackData->Address[3];
540   }
541 
542   return;
543 }
544 
Windows_ioctlProbeTargetDrive(const char * id3args_drive,char * mcdi_data)545 uint16_t Windows_ioctlProbeTargetDrive(const char *id3args_drive,
546                                        char *mcdi_data) {
547   uint16_t mcdi_data_len = 0;
548   char cd_device_path[16];
549 
550   memset(cd_device_path, 0, 16);
551   sprintf(cd_device_path, "\\\\.\\%s:", id3args_drive);
552 
553   HANDLE cdrom_device = APar_OpenFileWin32(cd_device_path,
554                                            GENERIC_READ,
555                                            FILE_SHARE_READ,
556                                            NULL,
557                                            OPEN_EXISTING,
558                                            FILE_ATTRIBUTE_NORMAL,
559                                            NULL);
560   if (cdrom_device != INVALID_HANDLE_VALUE) {
561     Windows_ioctlReadCDTOC(cdrom_device);
562     if (cdTOC != NULL) {
563       uint8_t cdType = DetermineCDType(cdTOC);
564       if (cdType == CDOBJECT_AUDIOCD) {
565         mcdi_data_len = FormMCDIdata(mcdi_data);
566       }
567     }
568     CloseHandle(cdrom_device);
569   }
570 
571   return mcdi_data_len;
572 }
573 #endif
574 
575 ////////////////////////////////////////////////////////////////////////////
576 //                      CD TOC Entry Area                                 //
577 ////////////////////////////////////////////////////////////////////////////
578 
GenerateMCDIfromCD(const char * drive,char * dest_buffer)579 uint16_t GenerateMCDIfromCD(const char *drive, char *dest_buffer) {
580   uint16_t mcdi_bytes = 0;
581 #if defined(__APPLE__)
582   mcdi_bytes = OSX_ProbeTargetDrive(drive, dest_buffer);
583 #elif defined(__linux__)
584   mcdi_bytes = Linux_ioctlProbeTargetDrive(drive, dest_buffer);
585 #elif defined(_WIN32)
586   mcdi_bytes = Windows_ioctlProbeTargetDrive(drive, dest_buffer);
587 #endif
588   return mcdi_bytes;
589 }
590