1 /* bcd.c -- Brennan's CD-ROM Audio Playing Library
2 by Brennan Underwood, http://brennan.home.ml.org/ */
3 //edited on 9/15/98 by Victor Rachels to stick it in d1x without extra crap
21 static int mscdex_version;
22 static int first_drive;
23 static int num_tracks;
24 static int lowest_track, highest_track;
25 static int audio_length;
29 static int dos_mem_segment, dos_mem_selector = -1;
31 int _status, _error, _error_code;
32 char *_bcd_error = NULL;
34 #define RESET_ERROR (_error = _error_code = 0)
35 #define ERROR_BIT (1 << 15)
36 #define BUSY_BIT (1 << 9)
44 /* I know 'typedef struct {} bleh' is a bad habit, but... */
46 unsigned char len __attribute__((packed));
47 unsigned char unit __attribute__((packed));
48 unsigned char command __attribute__((packed));
49 unsigned short status __attribute__((packed));
50 unsigned char reserved[8] __attribute__((packed));
54 RequestHeader request_header __attribute__((packed));
55 unsigned char descriptor __attribute__((packed));
56 unsigned long address __attribute__((packed));
57 unsigned short len __attribute__((packed));
58 unsigned short secnum __attribute__((packed));
59 unsigned long ptr __attribute__((packed));
63 unsigned char control __attribute__((packed));
64 unsigned char lowest __attribute__((packed));
65 unsigned char highest __attribute__((packed));
66 unsigned char total[4] __attribute__((packed));
70 unsigned char control __attribute__((packed));
71 unsigned char track_number __attribute__((packed));
72 unsigned char start[4] __attribute__((packed));
73 unsigned char info __attribute__((packed));
77 RequestHeader request __attribute__((packed));
78 unsigned char mode __attribute__((packed));
79 unsigned long start __attribute__((packed));
80 unsigned long len __attribute__((packed));
84 RequestHeader request __attribute__((packed));
88 RequestHeader request __attribute__((packed));
92 unsigned char control __attribute__((packed));
93 unsigned char input0 __attribute__((packed));
94 unsigned char volume0 __attribute__((packed));
95 unsigned char input1 __attribute__((packed));
96 unsigned char volume1 __attribute__((packed));
97 unsigned char input2 __attribute__((packed));
98 unsigned char volume2 __attribute__((packed));
99 unsigned char input3 __attribute__((packed));
100 unsigned char volume3 __attribute__((packed));
104 unsigned char control __attribute__((packed));
105 unsigned char fn __attribute__((packed));
109 unsigned char control __attribute__((packed));
110 unsigned char mbyte __attribute__((packed));
111 } MediaChangedRequest;
114 unsigned char control __attribute__((packed));
115 unsigned long status __attribute__((packed));
119 unsigned char control __attribute__((packed));
120 unsigned char mode __attribute__((packed));
121 unsigned long loc __attribute__((packed));
126 char *bcd_error(void)
128 static char retstr[132];
129 char *errorcodes[] = { "Write-protect violation",
134 "Bad drive request structure length",
138 "Printer out of paper: world coming to an end",/* I mean really, on a CD? */
144 "Invalid disk change" };
149 strcat(retstr, "Device error: ");
150 if (_error_code < 0 || _error_code > 0xf)
151 strcat(retstr, "Invalid error");
153 strcat(retstr, errorcodes[_error_code]);
156 if (_bcd_error != NULL)
158 if (*retstr) strcat(retstr, ", ");
159 strcat(retstr, "BCD error: ");
160 strcat(retstr, _bcd_error);
165 /* DOS IOCTL w/ command block */
166 static void bcd_ioctl(IOCTLI *ioctli, void *command, int len) {
167 int ioctli_len = sizeof(IOCTLI);
168 unsigned long command_address = dos_mem_segment << 4;
171 memset(®s, 0, sizeof regs);
172 regs.x.es = (__tb >> 4) & 0xffff;
174 regs.x.bx = __tb & 0xf;
175 regs.x.cx = first_drive;
176 ioctli->address = dos_mem_segment << 16;
178 dosmemput(ioctli, ioctli_len, __tb); /* put ioctl into dos area */
179 dosmemput(command, len, command_address); /* and command too */
180 if (__dpmi_int(0x2f, ®s) == -1) {
181 _bcd_error = "__dpmi_int() failed";
184 dosmemget(__tb, ioctli_len, ioctli); /* retrieve results */
185 dosmemget(command_address, len, command);
186 _status = ioctli->request_header.status;
187 if (_status & ERROR_BIT) {
189 _error_code = _status & 0xff;
196 /* no command block */
197 static void bcd_ioctl2(void *cmd, int len) {
199 memset(®s, 0, sizeof regs);
200 regs.x.es = (__tb >> 4) & 0xffff;
202 regs.x.bx = __tb & 0xf;
203 regs.x.cx = first_drive;
204 dosmemput(cmd, len, __tb); /* put ioctl block in dos arena */
205 if (__dpmi_int(0x2f, ®s) == -1) {
206 _bcd_error = "__dpmi_int() failed";
209 /* I hate to have no error capability for ioctl2 but the command block
210 doesn't necessarily have a status field */
214 static int red2hsg(char *r) {
215 return r[0] + r[1]*75 + r[2]*4500 - 150;
218 int bcd_now_playing(void) {
219 int i, loc = bcd_audio_position();
221 if (!bcd_audio_busy()) {
222 _bcd_error = "Audio not playing";
225 if (tracks == NULL && !bcd_get_audio_info())
227 for (i = lowest_track; i <= highest_track; i++) {
228 if (loc >= tracks[i].start && loc <= tracks[i].end) return i;
230 /* some bizarre location? */
231 _bcd_error = "Head outside of bounds";
235 /* handles the setup for CD-ROM audio interface */
240 /* disk I/O wouldn't work anyway if you set sizeof tb this low, but... */
241 if (_go32_info_block.size_of_transfer_buffer < 4096) {
242 _bcd_error = "Transfer buffer too small";
246 memset(®s, 0, sizeof regs);
249 __dpmi_int(0x2f, ®s);
250 if (regs.x.bx == 0) { /* abba no longer lives */
251 _bcd_error = "MSCDEX not found";
255 first_drive = regs.x.cx; /* use the first drive */
257 /* check for mscdex at least 2.0 */
258 memset(®s, 0, sizeof regs);
260 __dpmi_int(0x2f, ®s);
261 if (regs.x.bx == 0) {
262 _bcd_error = "MSCDEX version < 2.0";
265 mscdex_version = regs.x.bx;
267 /* allocate 256 bytes of dos memory for the command blocks */
268 if ((dos_mem_segment = __dpmi_allocate_dos_memory(16, &dos_mem_selector))<0) {
269 _bcd_error = "Could not allocate 256 bytes of DOS memory";
273 atexit( (void(*)()) bcd_close );
274 return mscdex_version;
277 /* Shuts down CD-ROM audio interface */
278 int bcd_close(void) {
280 if (dos_mem_selector != -1) {
281 __dpmi_free_dos_memory(dos_mem_selector);
282 dos_mem_selector = -1;
284 #ifndef STATIC_TRACKS
285 if (tracks) free(tracks);
292 int bcd_open_door(void) {
296 memset(&ioctli, 0, sizeof ioctli);
297 ioctli.request_header.len = sizeof ioctli;
298 ioctli.request_header.command = 12;
300 bcd_ioctl(&ioctli, &eject, sizeof eject);
301 if (_error) return 0;
305 int bcd_close_door(void) {
309 memset(&ioctli, 0, sizeof ioctli);
310 ioctli.request_header.len = sizeof ioctli;
311 ioctli.request_header.command = 12;
313 bcd_ioctl(&ioctli, &closeit, sizeof closeit);
314 if (_error) return 0;
318 int bcd_lock(int fn) {
322 memset(&ioctli, 0, sizeof ioctli);
323 memset(&req, 0, sizeof req);
324 ioctli.request_header.len = sizeof ioctli;
325 ioctli.request_header.command = 12;
326 ioctli.len = sizeof req;
329 bcd_ioctl(&ioctli, &req, sizeof req);
330 if (_error) return 0;
335 int bcd_disc_changed(void) {
337 MediaChangedRequest req;
339 memset(&ioctli, 0, sizeof ioctli);
340 memset(&req, 0, sizeof req);
341 ioctli.request_header.len = sizeof ioctli;
342 ioctli.request_header.command = 3;
343 ioctli.len = sizeof req;
345 bcd_ioctl(&ioctli, &req, sizeof req);
349 int bcd_reset(void) {
354 memset(&ioctli, 0, sizeof ioctli);
355 ioctli.request_header.len = sizeof ioctli;
356 ioctli.request_header.command = 12;
358 bcd_ioctl(&ioctli, &reset, sizeof reset);
359 if (_error) return 0;
363 int bcd_device_status(void) {
367 memset(&ioctli, 0, sizeof ioctli);
368 memset(&req, 0, sizeof req);
369 ioctli.request_header.len = sizeof ioctli; // ok
370 ioctli.request_header.command = 3;
371 ioctli.len = sizeof req;
373 bcd_ioctl(&ioctli, &req, sizeof req);
377 static int bcd_get_status_word(void) {
381 /* get cd info as an excuse to get a look at the status word */
382 memset(&disk_info, 0, sizeof disk_info);
383 memset(&ioctli, 0, sizeof ioctli);
385 ioctli.request_header.len = 26;
386 ioctli.request_header.command = 3;
388 disk_info.control = 10;
389 bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
393 int bcd_audio_busy(void) {
395 /* If the door is open, then the head is busy, and so the busy bit is
396 on. It is not, however, playing audio. */
397 if (bcd_device_status() & BCD_DOOR_OPEN)
400 bcd_get_status_word();
401 if (_error) return -1;
402 return (_status & BUSY_BIT) ? 1 : 0;
405 int bcd_audio_position(void) {
409 memset(&ioctli, 0, sizeof ioctli);
410 memset(&req, 0, sizeof req);
411 ioctli.request_header.len = sizeof ioctli;
412 ioctli.request_header.command = 3;
413 ioctli.len = sizeof req;
415 bcd_ioctl(&ioctli, &req, sizeof req);
419 /* Internal function to get track info */
420 static void bcd_get_track_info(int n, Track *t) {
424 memset(&ioctli, 0, sizeof ioctli);
425 memset(&info, 0, sizeof info);
426 ioctli.request_header.len = sizeof ioctli;
427 ioctli.request_header.command = 3;
429 info.track_number = n;
430 bcd_ioctl(&ioctli, &info, sizeof info);
431 t->start = red2hsg(info.start);
438 int bcd_get_audio_info(void) {
446 if (tracks) free(tracks);
449 memset(&disk_info, 0, sizeof disk_info);
450 memset(&ioctli, 0, sizeof ioctli);
452 ioctli.request_header.len = 26;
453 ioctli.request_header.command = 3;
455 disk_info.control = 10;
456 bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
457 if (_error) return 0;
459 lowest_track = disk_info.lowest;
460 highest_track = disk_info.highest;
461 if(lowest_track > highest_track)
463 num_tracks = highest_track - lowest_track + 1;
465 /* alloc max space in order to attempt to avoid possible overrun bug */
466 tracks = calloc(highest_track+1, sizeof(Track));
467 if (tracks == NULL) {
468 _bcd_error = "Out of memory allocating tracks\n";
472 /* get track starts */
473 for (i = lowest_track; i <= highest_track; i++)
474 bcd_get_track_info(i, tracks+i);
476 /* figure out track ends */
477 for (i = lowest_track; i < highest_track; i++)
478 tracks[i].end = tracks[i+1].start-1;
479 audio_length = red2hsg(disk_info.total);
480 tracks[i].end = audio_length;
481 for (i = lowest_track; i <= highest_track; i++)
482 tracks[i].len = tracks[i].end - tracks[i].start;
487 int bcd_get_track_address(int trackno, int *start, int *len) {
489 //if (trackno >= num_tracks+1 || trackno <= 0) {
490 if (trackno < lowest_track || trackno > highest_track) {
491 _bcd_error = "Track out of range";
495 *start = tracks[trackno].start;
496 *len = tracks[trackno].len;
500 int bcd_track_is_audio(int trackno) {
501 //if (trackno >= num_tracks+1 || trackno <= 0) {
502 if (trackno < lowest_track || trackno > highest_track) {
503 _bcd_error = "Track out of range";
506 return tracks[trackno].is_audio;
509 int bcd_set_volume(int volume) {
514 if (volume > 255) volume = 255;
515 else if (volume < 0) volume = 0;
516 memset(&ioctli, 0, sizeof ioctli);
517 ioctli.request_header.len = sizeof ioctli;
518 ioctli.request_header.command = 12;
519 ioctli.len = sizeof v;
530 bcd_ioctl(&ioctli, &v, sizeof v);
531 if (_error) return 0;
535 int bcd_play(int location, int frames) {
537 memset(&cmd, 0, sizeof cmd);
540 /* the following should be in user code, but it'll fail otherwise */
541 if (bcd_audio_busy())
544 cmd.request.len = sizeof cmd;
545 cmd.request.command = 132;
546 cmd.start = location;
548 bcd_ioctl2(&cmd, sizeof cmd);
549 if (_error) return 0;
553 int bcd_play_track(int *trackno) {
555 if (!bcd_get_audio_info()) return 0;
557 if (*trackno < lowest_track)
558 *trackno = highest_track;
559 else if (*trackno > highest_track)
560 *trackno = lowest_track;
562 if (! tracks[*trackno].is_audio)
564 _bcd_error = "Not an audio track";
568 return bcd_play(tracks[*trackno].start, tracks[*trackno].len);
574 memset(&cmd, 0, sizeof cmd);
575 cmd.request.len = sizeof cmd;
576 cmd.request.command = 133;
577 bcd_ioctl2(&cmd, sizeof cmd);
578 if (_error) return 0;
582 int bcd_resume(void) {
585 memset(&cmd, 0, sizeof cmd);
586 cmd.request.len = sizeof cmd;
587 cmd.request.command = 136;
588 bcd_ioctl2(&cmd, sizeof cmd);
589 if (_error) return 0;
598 static char *card(int c) {
599 return c == 1 ? "" : "s";
602 static void print_hsg(int hsg) {
603 int hours, minutes, seconds;
605 minutes = seconds / 60;
607 hours = minutes / 60;
609 printf("%2d:%02d:%02d", hours, minutes, seconds);
612 static void print_binary(int v, int len) {
614 printf("%d", (v & (1 << len)) ? 1 : 0);
617 int main(int argc, char *argv[]) {
621 fprintf(stderr, "Couldn't open CD-ROM drive. %s\n", bcd_error());
625 for (i = 1; i < argc; i++) {
626 if (*argv[i] == '-') strcpy(argv[i], argv[i]+1);
627 if (!strcmp(argv[i], "open") || !strcmp(argv[i], "eject")) {
629 } else if (!strcmp(argv[i], "close")) {
631 } else if (!strcmp(argv[i], "sleep")) {
632 if (++i >= argc) break;
633 sleep(atoi(argv[i]));
634 } else if (!strcmp(argv[i], "list")) {
635 int nd = 0, na = 0, audio_time = 0;
636 if (!bcd_get_audio_info()) {
637 printf("Error getting audio info\n");
638 } else if (lowest_track == 0) {
639 printf("No audio tracks\n");
641 for (t = lowest_track; t <= highest_track; t++) {
642 printf("Track %2d: ", t);
643 print_hsg(tracks[t].start);
645 print_hsg(tracks[t].end);
647 print_hsg(tracks[t].len);
648 if (tracks[t].is_audio) {
651 audio_time += tracks[t].len;
656 printf(" (HSG: %06d->%06d)\n", tracks[t].start, tracks[t].end);
658 printf("%d audio track%s, %d data track%s\n", na, card(na), nd, card(nd));
660 printf("Audio time: ");
661 print_hsg(audio_time);
665 } else if (!strcmp(argv[i], "lock")) {
667 } else if (!strcmp(argv[i], "pladdr")) {
668 if (++i >= argc) break;
670 if (++i >= argc) break;
672 printf("Playing frame %d to frame %d\n", n1, n2);
674 } else if (!strcmp(argv[i], "play")) {
675 if (++i >= argc) break;
676 if (bcd_audio_busy()) {
681 printf("Playing track %d\n", n1);
683 } else if (!strcmp(argv[i], "reset")) {
685 } else if (!strcmp(argv[i], "resume")) {
687 } else if (!strcmp(argv[i], "status")) {
689 s = bcd_device_status();
690 printf("MSCDEX version %d.%d\n", mscdex_version >> 8,
691 mscdex_version & 0xff);
692 printf("Device status word '");
694 printf("'\nDoor is %sopen\n", s & BCD_DOOR_OPEN ? "" : "not ");
695 printf("Door is %slocked\n", s & BCD_DOOR_UNLOCKED ? "not " : "");
696 printf("Audio is %sbusy\n", bcd_audio_busy() ? "" : "not ");
697 s = bcd_disc_changed();
698 if (s == BCD_DISC_UNKNOWN) printf("Media change status unknown\n");
699 else printf("Media has %schanged\n",
700 (s == BCD_DISC_CHANGED) ? "" : "not ");
701 } else if (!strcmp(argv[i], "stop")) {
703 } else if (!strcmp(argv[i], "unlock")) {
705 } else if (!strcmp(argv[i], "volume")) {
706 bcd_set_volume(atoi(argv[++i]));
707 } else if (!strcmp(argv[i], "wait")) {
708 while (bcd_audio_busy()) {
709 int n = bcd_now_playing();
712 print_hsg(bcd_audio_position() - tracks[n].start);
716 if (kbhit() && getch() == 27) break;
719 } else if (!strcmp(argv[i], "help") || !strcmp(argv[i], "usage") ||
720 !strcmp(argv[i], "?") || !strcmp(argv[i], "/?") ) {
721 printf("BCD version %x.%x\n" \
722 "Usage: BCD {commands}\n" \
723 "Valid commands:\n" \
724 "\tclose - close door/tray\n" \
725 "\tdelay {n} - delay {n} seconds\n" \
726 "\tlist - list track info\n" \
727 "\tlock - lock door/tray\n" \
728 "\topen - open door/tray\n" \
729 "\tpladdr {n1} {n2}- play frame {n1} to {n2}\n" \
730 "\tplay {n} - play track {n}\n" \
731 "\treset - reset the drive\n" \
732 "\tresume - resume from last stop\n" \
733 "\tstatus - show drive status\n" \
734 "\tstop - stop audio playback\n" \
735 "\tunlock - unlock door/tray\n" \
736 "\tvolume {n} - set volume to {n} where 0 <= {n} <= 255\n",
737 BCD_VERSION >> 8, BCD_VERSION & 0xff);
739 printf("Unknown command '%s'\n", argv[i]);
740 if (_error || _bcd_error) printf("%s\n", bcd_error());