]> icculus.org git repositories - btb/d2x.git/blob - arch/dos/bcd.c
Little fixes
[btb/d2x.git] / arch / dos / bcd.c
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
4
5 #include <dos.h>
6 #include <dpmi.h>
7 #include <go32.h>
8 #include <fcntl.h>
9 #include <stdio.h>
10 #include <malloc.h>
11 #include <unistd.h>
12 #include <strings.h>
13
14 #include "bcd.h"
15
16 typedef struct {
17   int is_audio;
18   int start, end, len;
19 } Track;
20
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;
26
27 static Track *tracks;
28
29 static int dos_mem_segment, dos_mem_selector = -1;
30
31 int _status, _error, _error_code;
32 char *_bcd_error = NULL;
33
34 #define RESET_ERROR (_error = _error_code = 0)
35 #define ERROR_BIT (1 << 15)
36 #define BUSY_BIT (1 << 9)
37 #ifndef TRUE
38 #define TRUE 1
39 #define FALSE 0
40 #endif
41
42 #pragma pack(1)
43
44 /* I know 'typedef struct {} bleh' is a bad habit, but... */
45 typedef struct {
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));
51 } RequestHeader;
52
53 typedef struct {
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));
60 } IOCTLI;
61
62 typedef struct {
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));
67 } DiskInfo;
68
69 typedef struct {
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));
74 } TrackInfo;
75
76 typedef struct {
77   RequestHeader request __attribute__((packed));
78   unsigned char mode    __attribute__((packed));
79   unsigned long start   __attribute__((packed));
80   unsigned long len     __attribute__((packed));
81 } PlayRequest;
82
83 typedef struct {
84   RequestHeader request __attribute__((packed));
85 } StopRequest;
86
87 typedef struct {
88   RequestHeader request __attribute__((packed));
89 } ResumeRequest;
90
91 typedef struct {
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));
101 } VolumeRequest;
102
103 typedef struct {
104   unsigned char control __attribute__((packed));
105   unsigned char fn      __attribute__((packed));
106 } LockRequest;
107
108 typedef struct {
109   unsigned char control __attribute__((packed));
110   unsigned char mbyte   __attribute__((packed));
111 } MediaChangedRequest;
112
113 typedef struct {
114   unsigned char control __attribute__((packed));
115   unsigned long status  __attribute__((packed));
116 } StatusRequest;
117
118 typedef struct {
119   unsigned char control __attribute__((packed));
120   unsigned char mode    __attribute__((packed));
121   unsigned long loc     __attribute__((packed));
122 } PositionRequest;
123
124 #pragma pack()
125
126 char *bcd_error(void)
127 {
128   static char retstr[132];
129   char *errorcodes[] = { "Write-protect violation",
130                          "Unknown unit",
131                          "Drive not ready",
132                          "Unknown command",
133                          "CRC error",
134                          "Bad drive request structure length",
135                          "Seek error",
136                          "Unknown media",
137                          "Sector not found",
138                          "Printer out of paper: world coming to an end",/* I mean really, on a CD? */
139                          "Write fault",
140                          "Read fault",
141                          "General failure",
142                          "Reserved",
143                          "Reserved",
144                          "Invalid disk change" };
145   *retstr = 0;
146
147     if (_error != 0)
148      {
149        strcat(retstr, "Device error: ");
150         if (_error_code < 0 || _error_code > 0xf)
151          strcat(retstr, "Invalid error");
152         else
153          strcat(retstr, errorcodes[_error_code]);
154        strcat(retstr, "  ");
155      }
156     if (_bcd_error != NULL)
157      {
158         if (*retstr) strcat(retstr, ", ");
159          strcat(retstr, "BCD error: ");
160        strcat(retstr, _bcd_error);
161      }
162    return retstr;
163 }
164
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;
169   __dpmi_regs regs;
170
171   memset(&regs, 0, sizeof regs);
172   regs.x.es = (__tb >> 4) & 0xffff;
173   regs.x.ax = 0x1510;
174   regs.x.bx = __tb & 0xf;
175   regs.x.cx = first_drive;
176   ioctli->address = dos_mem_segment << 16;
177   ioctli->len = len;
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, &regs) == -1) {
181     _bcd_error = "__dpmi_int() failed";
182     return;
183   }
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) {
188     _error = TRUE;
189     _error_code = _status & 0xff;
190   } else {
191     _error = FALSE;
192     _error_code = 0;
193   }
194 }
195
196 /* no command block */
197 static void bcd_ioctl2(void *cmd, int len) {
198   __dpmi_regs regs;
199   memset(&regs, 0, sizeof regs);
200   regs.x.es = (__tb >> 4) & 0xffff;
201   regs.x.ax = 0x1510;
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, &regs) == -1) {
206     _bcd_error = "__dpmi_int() failed";
207     return;
208   }
209   /* I hate to have no error capability for ioctl2 but the command block
210      doesn't necessarily have a status field */
211   RESET_ERROR;
212 }
213
214 static int red2hsg(char *r) {
215   return r[0] + r[1]*75 + r[2]*4500 - 150;
216 }
217
218 int bcd_now_playing(void) {
219   int i, loc = bcd_audio_position();
220   _bcd_error = NULL;
221   if (!bcd_audio_busy()) {
222     _bcd_error = "Audio not playing";
223     return 0;
224   }
225   if (tracks == NULL && !bcd_get_audio_info())
226     return 0;
227   for (i = lowest_track; i <= highest_track; i++) {
228     if (loc >= tracks[i].start && loc <= tracks[i].end) return i;
229   }
230   /* some bizarre location? */
231   _bcd_error = "Head outside of bounds";
232   return 0;
233 }
234
235 /* handles the setup for CD-ROM audio interface */
236 int bcd_open(void) {
237   __dpmi_regs regs;
238   _bcd_error = NULL;
239
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";
243     return 0;
244   }
245
246   memset(&regs, 0, sizeof regs);
247   regs.x.ax = 0x1500;
248   regs.x.bx = 0x0;
249   __dpmi_int(0x2f, &regs);
250   if (regs.x.bx == 0) { /* abba no longer lives */
251     _bcd_error = "MSCDEX not found";
252     return 0;
253   }
254
255   first_drive = regs.x.cx; /* use the first drive */
256
257   /* check for mscdex at least 2.0 */
258   memset(&regs, 0, sizeof regs);
259   regs.x.ax = 0x150C;
260   __dpmi_int(0x2f, &regs);
261   if (regs.x.bx == 0) {
262     _bcd_error = "MSCDEX version < 2.0";
263     return 0;
264   }
265   mscdex_version = regs.x.bx;
266
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";
270     return 0;
271   }
272
273   atexit( (void(*)()) bcd_close );
274   return mscdex_version;
275 }
276
277 /* Shuts down CD-ROM audio interface */
278 int bcd_close(void) {
279   _bcd_error = NULL;
280   if (dos_mem_selector != -1) {
281     __dpmi_free_dos_memory(dos_mem_selector);
282     dos_mem_selector = -1;
283   }
284 #ifndef STATIC_TRACKS
285   if (tracks) free(tracks);
286   tracks = NULL;
287 #endif
288   RESET_ERROR;
289   return 1;
290 }
291
292 int bcd_open_door(void) {
293   IOCTLI ioctli;
294   char eject = 0;
295   _bcd_error = NULL;
296   memset(&ioctli, 0, sizeof ioctli);
297   ioctli.request_header.len = sizeof ioctli;
298   ioctli.request_header.command = 12;
299   ioctli.len = 1;
300   bcd_ioctl(&ioctli, &eject, sizeof eject);
301   if (_error) return 0;
302   return 1;
303 }
304
305 int bcd_close_door(void) {
306   IOCTLI ioctli;
307   char closeit = 5;
308   _bcd_error = NULL;
309   memset(&ioctli, 0, sizeof ioctli);
310   ioctli.request_header.len = sizeof ioctli;
311   ioctli.request_header.command = 12;
312   ioctli.len = 1;
313   bcd_ioctl(&ioctli, &closeit, sizeof closeit);
314   if (_error) return 0;
315   return 1;
316 }
317
318 int bcd_lock(int fn) {
319   IOCTLI ioctli;
320   LockRequest req;
321   _bcd_error = NULL;
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;
327   req.control = 1;
328   req.fn = fn ? 1 : 0;
329   bcd_ioctl(&ioctli, &req, sizeof req);
330   if (_error) return 0;
331   return 1;
332 }
333
334
335 int bcd_disc_changed(void) {
336   IOCTLI ioctli;
337   MediaChangedRequest req;
338   _bcd_error = NULL;
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;
344   req.control = 9;
345   bcd_ioctl(&ioctli, &req, sizeof req);
346   return req.mbyte;
347 }
348
349 int bcd_reset(void) {
350   IOCTLI ioctli;
351   char reset = 2;
352   _bcd_error = NULL;
353
354   memset(&ioctli, 0, sizeof ioctli);
355   ioctli.request_header.len = sizeof ioctli;
356   ioctli.request_header.command = 12;
357   ioctli.len = 1;
358   bcd_ioctl(&ioctli, &reset, sizeof reset);
359   if (_error) return 0;
360   return 1;
361 }
362
363 int bcd_device_status(void) {
364   IOCTLI ioctli;
365   StatusRequest req;
366   _bcd_error = NULL;
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;
372   req.control = 6;
373   bcd_ioctl(&ioctli, &req, sizeof req);
374   return req.status;
375 }
376
377 static int bcd_get_status_word(void) {
378   IOCTLI ioctli;
379   DiskInfo disk_info;
380
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);
384
385   ioctli.request_header.len = 26;
386   ioctli.request_header.command = 3;
387   ioctli.len = 7;
388   disk_info.control = 10;
389   bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
390   return _status;
391 }
392
393 int bcd_audio_busy(void) {
394   _bcd_error = NULL;
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)
398     return 0;
399
400   bcd_get_status_word();
401   if (_error) return -1;
402   return (_status & BUSY_BIT) ? 1 : 0;
403 }
404
405 int bcd_audio_position(void) {
406   IOCTLI ioctli;
407   PositionRequest req;
408   _bcd_error = NULL;
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;
414   req.control = 1;
415   bcd_ioctl(&ioctli, &req, sizeof req);
416   return req.loc;
417 }
418
419 /* Internal function to get track info */
420 static void bcd_get_track_info(int n, Track *t) {
421   IOCTLI ioctli;
422   TrackInfo info;
423
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;
428   info.control = 11;
429   info.track_number = n;
430   bcd_ioctl(&ioctli, &info, sizeof info);
431   t->start = red2hsg(info.start);
432   if (info.info & 64)
433     t->is_audio = 0;
434   else
435     t->is_audio = 1;
436 }
437
438 int bcd_get_audio_info(void) {
439   IOCTLI ioctli;
440   DiskInfo disk_info;
441   int i;
442
443
444   _bcd_error = NULL;
445
446   if (tracks) free(tracks);
447   tracks = NULL;
448
449   memset(&disk_info, 0, sizeof disk_info);
450   memset(&ioctli, 0, sizeof ioctli);
451
452   ioctli.request_header.len = 26;
453   ioctli.request_header.command = 3;
454   ioctli.len = 7;
455   disk_info.control = 10;
456   bcd_ioctl(&ioctli, &disk_info, sizeof disk_info);
457   if (_error) return 0;
458
459   lowest_track = disk_info.lowest;
460   highest_track = disk_info.highest;
461    if(lowest_track > highest_track)
462     lowest_track = 1;
463   num_tracks = highest_track - lowest_track + 1;
464
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";
469     return 0;
470   }
471
472   /* get track starts */
473   for (i = lowest_track; i <= highest_track; i++)
474     bcd_get_track_info(i, tracks+i);
475  
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;
483
484   return num_tracks;
485 }
486
487 int bcd_get_track_address(int trackno, int *start, int *len) {
488   _bcd_error = NULL;
489   //if (trackno >= num_tracks+1 || trackno <= 0) {
490   if (trackno < lowest_track || trackno > highest_track) {
491     _bcd_error  = "Track out of range";
492     *start = *len = 0;
493     return 0;
494   }
495   *start = tracks[trackno].start;
496   *len = tracks[trackno].len;
497   return 1;
498 }
499
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";
504     return 0;
505   }
506   return tracks[trackno].is_audio;
507 }
508
509 int bcd_set_volume(int volume) {
510   IOCTLI ioctli;
511   VolumeRequest v;
512
513   _bcd_error = NULL;
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;
520   v.control = 3;
521   v.volume0 = volume;
522   v.input0 = 0;
523   v.volume1 = volume;
524   v.input1 = 1;
525   v.volume2 = volume;
526   v.input2 = 2;
527   v.volume3 = volume;
528   v.input3 = 3;
529   
530   bcd_ioctl(&ioctli, &v, sizeof v);
531   if (_error) return 0;
532   return 1;
533 }
534
535 int bcd_play(int location, int frames) {
536   PlayRequest cmd;
537   memset(&cmd, 0, sizeof cmd);
538
539   _bcd_error = NULL;
540   /* the following should be in user code, but it'll fail otherwise */
541   if (bcd_audio_busy())
542     bcd_stop();
543
544   cmd.request.len = sizeof cmd;
545   cmd.request.command = 132;
546   cmd.start = location;
547   cmd.len = frames;
548   bcd_ioctl2(&cmd, sizeof cmd);
549   if (_error) return 0;
550   return 1;
551 }
552
553 int bcd_play_track(int *trackno) {
554   _bcd_error = NULL;
555   if (!bcd_get_audio_info()) return 0;
556
557   if (*trackno < lowest_track)
558    *trackno = highest_track;
559   else if (*trackno > highest_track)
560    *trackno = lowest_track;
561
562   if (! tracks[*trackno].is_audio)
563    {
564     _bcd_error = "Not an audio track";
565     return 0;
566    }
567
568   return bcd_play(tracks[*trackno].start, tracks[*trackno].len);
569 }
570
571 int bcd_stop(void) {
572   StopRequest cmd;
573   _bcd_error = NULL;
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;
579   return 1;
580 }
581
582 int bcd_resume(void) {
583   ResumeRequest cmd;
584   _bcd_error = NULL;
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;
590   return 1;
591 }
592
593 #ifdef __cplusplus
594 }
595 #endif
596
597 #ifdef STANDALONE
598 static char *card(int c) {
599   return c == 1 ? "" : "s";
600 }
601
602 static void print_hsg(int hsg) {
603   int hours, minutes, seconds;
604   seconds = hsg / 75;
605   minutes = seconds / 60;
606   seconds %= 60;
607   hours = minutes / 60;
608   minutes %= 60;
609   printf("%2d:%02d:%02d", hours, minutes, seconds);
610 }
611
612 static void print_binary(int v, int len) {
613   for (;len;len--)
614     printf("%d", (v & (1 << len)) ? 1 : 0);
615 }
616
617 int main(int argc, char *argv[]) {
618   int i, n1, n2, t;
619
620   if (!bcd_open()) {
621     fprintf(stderr, "Couldn't open CD-ROM drive. %s\n", bcd_error());
622     exit(0);
623   }
624   
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")) {
628       bcd_open_door();
629     } else if (!strcmp(argv[i], "close")) {
630       bcd_close_door();
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");
640       } else {
641         for (t = lowest_track; t <= highest_track; t++) {
642           printf("Track %2d: ", t);
643           print_hsg(tracks[t].start);
644           printf(" -> ");
645           print_hsg(tracks[t].end);
646           printf(" (");
647           print_hsg(tracks[t].len);
648           if (tracks[t].is_audio) {
649             na++;
650             printf(") audio");
651             audio_time += tracks[t].len;
652           } else {
653             nd++;
654             printf(") data ");
655           }
656           printf(" (HSG: %06d->%06d)\n", tracks[t].start, tracks[t].end);
657         }
658         printf("%d audio track%s, %d data track%s\n", na, card(na), nd, card(nd));
659         if (audio_time) {
660           printf("Audio time: ");
661           print_hsg(audio_time);
662           printf("\n");
663         }
664       }
665     } else if (!strcmp(argv[i], "lock")) {
666       bcd_lock(1);
667     } else if (!strcmp(argv[i], "pladdr")) {
668       if (++i >= argc) break;
669       n1 = atoi(argv[i]);
670       if (++i >= argc) break;
671       n2 = atoi(argv[i]);
672       printf("Playing frame %d to frame %d\n", n1, n2);
673       bcd_play(n1, n2-n1);
674     } else if (!strcmp(argv[i], "play")) {
675       if (++i >= argc) break;
676       if (bcd_audio_busy()) {
677         bcd_stop();
678         delay(1000);
679       }
680       n1 = atoi(argv[i]);
681       printf("Playing track %d\n", n1);
682       bcd_play_track(n1);
683     } else if (!strcmp(argv[i], "reset")) {
684       bcd_reset();
685     } else if (!strcmp(argv[i], "resume")) {
686       bcd_resume();
687     } else if (!strcmp(argv[i], "status")) {
688       int s;
689       s = bcd_device_status();
690       printf("MSCDEX version %d.%d\n", mscdex_version >> 8,
691              mscdex_version & 0xff);
692       printf("Device status word '");
693       print_binary(s, 16);
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")) {
702       bcd_stop();
703     } else if (!strcmp(argv[i], "unlock")) {
704       bcd_lock(0);
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();
710         if (n == 0) break;
711         printf("%2d: ", n);
712         print_hsg(bcd_audio_position() - tracks[n].start);
713         printf("\r");
714         fflush(stdout);
715         delay(100);
716         if (kbhit() && getch() == 27) break;
717       }
718       printf("\n");
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);
738     } else
739       printf("Unknown command '%s'\n", argv[i]);
740     if (_error || _bcd_error) printf("%s\n", bcd_error());
741   }
742   bcd_close();
743   exit(0);
744 }
745 #endif