1 /* Copyright  (C) 2010-2019 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (cdrom.c).
5 * ---------------------------------------------------------------------------------------
6 *
7 * Permission is hereby granted, free of charge,
8 * to any person obtaining a copy of this software and associated documentation files (the "Software"),
9 * to deal in the Software without restriction, including without limitation the rights to
10 * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software,
11 * and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
12 *
13 * The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
14 *
15 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED,
16 * INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
18 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
19 * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
21 */
22 
23 #ifdef HAVE_CONFIG_H
24 #include "config.h"
25 #endif
26 
27 #include <cdrom/cdrom.h>
28 #include <libretro.h>
29 #include <stdio.h>
30 #include <string.h>
31 #include <compat/strl.h>
32 #include <retro_math.h>
33 #include <retro_timers.h>
34 #include <streams/file_stream.h>
35 #include <retro_endianness.h>
36 #include <retro_miscellaneous.h>
37 #include <vfs/vfs_implementation.h>
38 #include <lists/string_list.h>
39 #include <lists/dir_list.h>
40 #include <string/stdstring.h>
41 #include <memalign.h>
42 
43 #include <math.h>
44 #ifdef _WIN32
45 #include <direct.h>
46 #else
47 #include <unistd.h>
48 #endif
49 
50 #if defined(__linux__) && !defined(ANDROID)
51 #include <sys/ioctl.h>
52 #include <scsi/sg.h>
53 #endif
54 
55 #if defined(_WIN32) && !defined(_XBOX)
56 #include <windows.h>
57 #include <winioctl.h>
58 #include <ntddscsi.h>
59 #endif
60 
61 #define CDROM_CUE_TRACK_BYTES 107
62 #define CDROM_MAX_SENSE_BYTES 16
63 #define CDROM_MAX_RETRIES 10
64 
65 typedef enum
66 {
67    DIRECTION_NONE,
68    DIRECTION_IN,
69    DIRECTION_OUT
70 } CDROM_CMD_Direction;
71 
cdrom_lba_to_msf(unsigned lba,unsigned char * min,unsigned char * sec,unsigned char * frame)72 void cdrom_lba_to_msf(unsigned lba, unsigned char *min, unsigned char *sec, unsigned char *frame)
73 {
74    if (!min || !sec || !frame)
75       return;
76 
77    *frame = lba % 75;
78    lba /= 75;
79    *sec = lba % 60;
80    lba /= 60;
81    *min = lba;
82 }
83 
cdrom_msf_to_lba(unsigned char min,unsigned char sec,unsigned char frame)84 unsigned cdrom_msf_to_lba(unsigned char min, unsigned char sec, unsigned char frame)
85 {
86    return (min * 60 + sec) * 75 + frame;
87 }
88 
increment_msf(unsigned char * min,unsigned char * sec,unsigned char * frame)89 void increment_msf(unsigned char *min, unsigned char *sec, unsigned char *frame)
90 {
91    if (!min || !sec || !frame)
92       return;
93 
94    *min = (*frame == 74) ? (*sec < 59 ? *min : *min + 1) : *min;
95    *sec = (*frame == 74) ? (*sec < 59 ? (*sec + 1) : 0) : *sec;
96    *frame = (*frame < 74) ? (*frame + 1) : 0;
97 }
98 
cdrom_print_sense_data(const unsigned char * sense,size_t len)99 static void cdrom_print_sense_data(const unsigned char *sense, size_t len)
100 {
101    unsigned i;
102    const char *sense_key_text = NULL;
103    unsigned char key;
104    unsigned char asc;
105    unsigned char ascq;
106 
107    if (len < 16)
108    {
109       printf("[CDROM] Sense data buffer length too small.\n");
110       fflush(stdout);
111       return;
112    }
113 
114    key = sense[2] & 0xF;
115    asc = sense[12];
116    ascq = sense[13];
117 
118    printf("[CDROM] Sense Data: ");
119 
120    for (i = 0; i < MIN(len, 16); i++)
121    {
122       printf("%02X ", sense[i]);
123    }
124 
125    printf("\n");
126 
127    if (sense[0] == 0x70)
128       printf("[CDROM] CURRENT ERROR:\n");
129    if (sense[0] == 0x71)
130       printf("[CDROM] DEFERRED ERROR:\n");
131 
132    switch (key)
133    {
134       case 0:
135          sense_key_text = "NO SENSE";
136          break;
137       case 1:
138          sense_key_text = "RECOVERED ERROR";
139          break;
140       case 2:
141          sense_key_text = "NOT READY";
142          break;
143       case 3:
144          sense_key_text = "MEDIUM ERROR";
145          break;
146       case 4:
147          sense_key_text = "HARDWARE ERROR";
148          break;
149       case 5:
150          sense_key_text = "ILLEGAL REQUEST";
151          break;
152       case 6:
153          sense_key_text = "UNIT ATTENTION";
154          break;
155       case 7:
156          sense_key_text = "DATA PROTECT";
157          break;
158       case 8:
159          sense_key_text = "BLANK CHECK";
160          break;
161       case 9:
162          sense_key_text = "VENDOR SPECIFIC";
163          break;
164       case 10:
165          sense_key_text = "COPY ABORTED";
166          break;
167       case 11:
168          sense_key_text = "ABORTED COMMAND";
169          break;
170       case 13:
171          sense_key_text = "VOLUME OVERFLOW";
172          break;
173       case 14:
174          sense_key_text = "MISCOMPARE";
175          break;
176    }
177 
178    printf("[CDROM] Sense Key: %02X (%s)\n", key, sense_key_text);
179    printf("[CDROM] ASC: %02X\n", asc);
180    printf("[CDROM] ASCQ: %02X\n", ascq);
181 
182    switch (key)
183    {
184       case 2:
185       {
186          switch (asc)
187          {
188             case 4:
189             {
190                switch (ascq)
191                {
192                   case 1:
193                      printf("[CDROM] Description: LOGICAL UNIT IS IN PROCESS OF BECOMING READY\n");
194                      break;
195                   default:
196                      break;
197                }
198 
199                break;
200             }
201             case 0x3a:
202             {
203                switch (ascq)
204                {
205                   case 0:
206                      printf("[CDROM] Description: MEDIUM NOT PRESENT\n");
207                      break;
208                   case 3:
209                      printf("[CDROM] Description: MEDIUM NOT PRESENT - LOADABLE\n");
210                      break;
211                   case 1:
212                      printf("[CDROM] Description: MEDIUM NOT PRESENT - TRAY CLOSED\n");
213                      break;
214                   case 2:
215                      printf("[CDROM] Description: MEDIUM NOT PRESENT - TRAY OPEN\n");
216                      break;
217                   default:
218                      break;
219                }
220 
221                break;
222             }
223             default:
224                break;
225          }
226       }
227       case 3:
228       {
229          if (asc == 0x11 && ascq == 0x5)
230             printf("[CDROM] Description: L-EC UNCORRECTABLE ERROR\n");
231          break;
232       }
233       case 5:
234       {
235          if (asc == 0x20 && ascq == 0)
236             printf("[CDROM] Description: INVALID COMMAND OPERATION CODE\n");
237          else if (asc == 0x24 && ascq == 0)
238             printf("[CDROM] Description: INVALID FIELD IN CDB\n");
239          else if (asc == 0x26 && ascq == 0)
240             printf("[CDROM] Description: INVALID FIELD IN PARAMETER LIST\n");
241          break;
242       }
243       case 6:
244       {
245          if (asc == 0x28 && ascq == 0)
246             printf("[CDROM] Description: NOT READY TO READY CHANGE, MEDIUM MAY HAVE CHANGED\n");
247          break;
248       }
249       default:
250          break;
251    }
252 
253    fflush(stdout);
254 }
255 
256 #if defined(_WIN32) && !defined(_XBOX)
cdrom_send_command_win32(const libretro_vfs_implementation_file * stream,CDROM_CMD_Direction dir,void * buf,size_t len,unsigned char * cmd,size_t cmd_len,unsigned char * sense,size_t sense_len)257 static int cdrom_send_command_win32(const libretro_vfs_implementation_file *stream, CDROM_CMD_Direction dir, void *buf, size_t len, unsigned char *cmd, size_t cmd_len, unsigned char *sense, size_t sense_len)
258 {
259    DWORD ioctl_bytes;
260    BOOL ioctl_rv;
261 #ifdef CDROM_DEBUG
262    clock_t t = clock();
263    const char *extra = " ";
264    static unsigned char last_min = 0;
265    static unsigned char last_sec = 0;
266    static unsigned char last_frame = 0;
267 
268    unsigned lba_cur = cdrom_msf_to_lba(last_min, last_sec, last_frame);
269    unsigned lba_req = cdrom_msf_to_lba(cmd[3], cmd[4], cmd[5]);
270 #endif
271    struct sptd_with_sense
272    {
273      SCSI_PASS_THROUGH_DIRECT s;
274      UCHAR sense[128];
275    } sptd;
276 
277    memset(&sptd, 0, sizeof(sptd));
278 
279    sptd.s.Length = sizeof(sptd.s);
280    sptd.s.CdbLength = cmd_len;
281 
282    switch (dir)
283    {
284       case DIRECTION_IN:
285          sptd.s.DataIn = SCSI_IOCTL_DATA_IN;
286          break;
287       case DIRECTION_OUT:
288          sptd.s.DataIn = SCSI_IOCTL_DATA_OUT;
289          break;
290       case DIRECTION_NONE:
291       default:
292          sptd.s.DataIn = SCSI_IOCTL_DATA_UNSPECIFIED;
293          break;
294    }
295 
296    sptd.s.TimeOutValue = 5;
297    sptd.s.DataBuffer = buf;
298    sptd.s.DataTransferLength = len;
299    sptd.s.SenseInfoLength = sizeof(sptd.sense);
300    sptd.s.SenseInfoOffset = offsetof(struct sptd_with_sense, sense);
301 
302    memcpy(sptd.s.Cdb, cmd, cmd_len);
303 
304    ioctl_rv = DeviceIoControl(stream->fh, IOCTL_SCSI_PASS_THROUGH_DIRECT, &sptd,
305       sizeof(sptd), &sptd, sizeof(sptd), &ioctl_bytes, NULL);
306 
307 #ifdef CDROM_DEBUG
308    if (lba_req < lba_cur)
309       extra = " BACKWARDS SECTOR READ";
310    else if (lba_req > lba_cur)
311       extra = " SKIPPED SECTOR READ";
312 
313    if (cmd[0] == 0xB9)
314    {
315       double time_taken = (double)(((clock() - t) * 1000) / CLOCKS_PER_SEC);
316       printf("time taken %f ms for DT received length %ld of %" PRId64 " for %02d:%02d:%02d to %02d:%02d:%02d%s req %d cur %d cur_lba %d\n", time_taken, sptd.s.DataTransferLength, len, cmd[3], cmd[4], cmd[5], cmd[6], cmd[7], cmd[8], extra, lba_req, lba_cur, stream->cdrom.cur_lba);
317       fflush(stdout);
318    }
319 
320    last_min = cmd[3];
321    last_sec = cmd[4];
322    last_frame = cmd[5];
323    increment_msf(&last_min, &last_sec, &last_frame);
324 #endif
325 
326    if (!ioctl_rv || sptd.s.ScsiStatus != 0)
327       return 1;
328 
329    return 0;
330 }
331 #endif
332 
333 #if defined(__linux__) && !defined(ANDROID)
cdrom_send_command_linux(const libretro_vfs_implementation_file * stream,CDROM_CMD_Direction dir,void * buf,size_t len,unsigned char * cmd,size_t cmd_len,unsigned char * sense,size_t sense_len)334 static int cdrom_send_command_linux(const libretro_vfs_implementation_file *stream, CDROM_CMD_Direction dir, void *buf, size_t len, unsigned char *cmd, size_t cmd_len, unsigned char *sense, size_t sense_len)
335 {
336    sg_io_hdr_t sgio = {0};
337    int rv;
338 
339    switch (dir)
340    {
341       case DIRECTION_IN:
342          sgio.dxfer_direction = SG_DXFER_FROM_DEV;
343          break;
344       case DIRECTION_OUT:
345          sgio.dxfer_direction = SG_DXFER_TO_DEV;
346          break;
347       case DIRECTION_NONE:
348       default:
349          sgio.dxfer_direction = SG_DXFER_NONE;
350          break;
351    }
352 
353    sgio.interface_id = 'S';
354    sgio.cmd_len = cmd_len;
355    sgio.cmdp = cmd;
356    sgio.dxferp = buf;
357    sgio.dxfer_len = len;
358    sgio.sbp = sense;
359    sgio.mx_sb_len = sense_len;
360    sgio.timeout = 5000;
361 
362    rv = ioctl(fileno(stream->fp), SG_IO, &sgio);
363 
364    if (rv == -1 || sgio.info & SG_INFO_CHECK)
365       return 1;
366 
367    return 0;
368 }
369 #endif
370 
cdrom_send_command(libretro_vfs_implementation_file * stream,CDROM_CMD_Direction dir,void * buf,size_t len,unsigned char * cmd,size_t cmd_len,size_t skip)371 static int cdrom_send_command(libretro_vfs_implementation_file *stream, CDROM_CMD_Direction dir, void *buf, size_t len, unsigned char *cmd, size_t cmd_len, size_t skip)
372 {
373    unsigned char *xfer_buf = NULL;
374    unsigned char *xfer_buf_pos = xfer_buf;
375    unsigned char sense[CDROM_MAX_SENSE_BYTES] = {0};
376    unsigned char retries_left = CDROM_MAX_RETRIES;
377    int i, rv = 0;
378    int frames = 1;
379    size_t padded_req_bytes;
380    size_t copied_bytes = 0;
381    bool read_cd = false;
382 
383    if (!cmd || cmd_len == 0)
384       return 1;
385 
386    if (cmd[0] == 0xBE || cmd[0] == 0xB9)
387    {
388       frames = ceil((len + skip) / 2352.0);
389       padded_req_bytes = 2352 * frames;
390       read_cd = true;
391       /* these will be incremented below */
392       cmd[6] = cmd[3];
393       cmd[7] = cmd[4];
394       cmd[8] = cmd[5];
395    }
396    else
397    {
398       padded_req_bytes = len + skip;
399    }
400 
401    xfer_buf = (unsigned char*)memalign_alloc(4096, padded_req_bytes);
402    xfer_buf_pos = xfer_buf;
403 
404    if (!xfer_buf)
405       return 1;
406 
407    memset(xfer_buf, 0, padded_req_bytes);
408 #ifdef CDROM_DEBUG
409    printf("Number of frames to read: %d\n", frames);
410    fflush(stdout);
411 #endif
412    for (i = 0; i < frames; i++)
413    {
414       size_t request_len = padded_req_bytes;
415       size_t copy_len = request_len;
416       bool cached_read = false;
417 
418       if (read_cd)
419       {
420          unsigned lba_req = 0;
421 
422          request_len = 2352;
423          copy_len = request_len;
424 
425          increment_msf(&cmd[6], &cmd[7], &cmd[8]);
426 
427          if (i > 0)
428          {
429             skip = 0;
430             increment_msf(&cmd[3], &cmd[4], &cmd[5]);
431          }
432          else
433          {
434             if (skip)
435                copy_len -= skip;
436          }
437 
438          if (i == frames - 1)
439          {
440             copy_len = len - copied_bytes;
441          }
442 
443          lba_req = cdrom_msf_to_lba(cmd[3], cmd[4], cmd[5]);
444 
445          if (stream->cdrom.last_frame_valid && lba_req == stream->cdrom.last_frame_lba)
446          {
447             /* use cached frame */
448             cached_read = true;
449 #ifdef CDROM_DEBUG
450             printf("[CDROM] Using cached frame\n");
451             fflush(stdout);
452 #endif
453             /* assumes request_len is always equal to the size of last_frame */
454             memcpy(xfer_buf_pos, stream->cdrom.last_frame, sizeof(stream->cdrom.last_frame));
455          }
456 
457       }
458 
459 #ifdef CDROM_DEBUG
460       if (!cached_read)
461       {
462          unsigned j;
463 
464          printf("[CDROM] Send Command: ");
465 
466          for (j = 0; j < cmd_len / sizeof(*cmd); j++)
467          {
468             printf("%02X ", cmd[j]);
469          }
470 
471          if (len)
472             printf("(buffer of size %" PRId64 " with skip bytes %" PRId64 " padded to %" PRId64 "), frame %d\n", len, skip, padded_req_bytes, i);
473          else
474             printf("\n");
475 
476          fflush(stdout);
477       }
478 #endif
479 
480 retry:
481 #if defined(__linux__) && !defined(ANDROID)
482       if (cached_read || !cdrom_send_command_linux(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense)))
483 #else
484 #if defined(_WIN32) && !defined(_XBOX)
485       if (cached_read || !cdrom_send_command_win32(stream, dir, xfer_buf_pos, request_len, cmd, cmd_len, sense, sizeof(sense)))
486 #endif
487 #endif
488       {
489          rv = 0;
490 
491          if (buf)
492          {
493 #if 0
494             printf("offsetting %" PRId64 " from buf, copying at xfer_buf offset %" PRId64 ", copying %" PRId64 " bytes\n", copied_bytes, (xfer_buf_pos + skip) - xfer_buf, copy_len);
495             fflush(stdout);
496 #endif
497             memcpy((char*)buf + copied_bytes, xfer_buf_pos + skip, copy_len);
498             copied_bytes += copy_len;
499 
500             if (read_cd && !cached_read && request_len >= 2352)
501             {
502                unsigned frame_end = cdrom_msf_to_lba(cmd[6], cmd[7], cmd[8]);
503 
504                /* cache the last received frame */
505                memcpy(stream->cdrom.last_frame, xfer_buf_pos, sizeof(stream->cdrom.last_frame));
506                stream->cdrom.last_frame_valid = true;
507                /* the ending frame is never actually read, so what we really just read is the one right before that */
508                stream->cdrom.last_frame_lba = frame_end - 1;
509             }
510             else
511                stream->cdrom.last_frame_valid = false;
512 
513 #if 0
514             printf("Frame %d, adding %" PRId64 " to buf_pos, is now %" PRId64 ". skip is %" PRId64 "\n", i, request_len, (xfer_buf_pos + request_len) - xfer_buf, skip);
515             fflush(stdout);
516 #endif
517             xfer_buf_pos += request_len;
518          }
519       }
520       else
521       {
522          cdrom_print_sense_data(sense, sizeof(sense));
523 
524          /* INQUIRY/TEST/SENSE should never fail, don't retry. */
525          /* READ ATIP seems to fail outright on some drives with pressed discs, skip retries. */
526          if (cmd[0] != 0x0 && cmd[0] != 0x12 && cmd[0] != 0x5A && !(cmd[0] == 0x43 && cmd[2] == 0x4))
527          {
528             unsigned char key = sense[2] & 0xF;
529 
530             switch (key)
531             {
532                case 0:
533                case 2:
534                case 3:
535                case 4:
536                case 6:
537                   if (retries_left)
538                   {
539    #ifdef CDROM_DEBUG
540                      printf("[CDROM] Read Retry...\n");
541                      fflush(stdout);
542    #endif
543                      retries_left--;
544                       retro_sleep(1000);
545                      goto retry;
546                   }
547                   else
548                   {
549                      rv = 1;
550    #ifdef CDROM_DEBUG
551                      printf("[CDROM] Read retries failed, giving up.\n");
552                      fflush(stdout);
553    #endif
554                   }
555 
556                   break;
557                default:
558                   break;
559             }
560          }
561 
562          rv = 1;
563       }
564    }
565 
566    if (xfer_buf)
567       memalign_free(xfer_buf);
568 
569    return rv;
570 }
571 
get_profile(unsigned short profile)572 static const char* get_profile(unsigned short profile)
573 {
574    switch (profile)
575    {
576       case 2:
577          return "Removable disk";
578          break;
579       case 8:
580          return "CD-ROM";
581          break;
582       case 9:
583          return "CD-R";
584          break;
585       case 0xA:
586          return "CD-RW";
587          break;
588       case 0x10:
589          return "DVD-ROM";
590          break;
591       case 0x11:
592          return "DVD-R Sequential Recording";
593          break;
594       case 0x12:
595          return "DVD-RAM";
596          break;
597       case 0x13:
598          return "DVD-RW Restricted Overwrite";
599          break;
600       case 0x14:
601          return "DVD-RW Sequential recording";
602          break;
603       case 0x15:
604          return "DVD-R Dual Layer Sequential Recording";
605          break;
606       case 0x16:
607          return "DVD-R Dual Layer Jump Recording";
608          break;
609       case 0x17:
610          return "DVD-RW Dual Layer";
611          break;
612       case 0x1A:
613          return "DVD+RW";
614          break;
615       case 0x1B:
616          return "DVD+R";
617          break;
618       case 0x2A:
619          return "DVD+RW Dual Layer";
620          break;
621       case 0x2B:
622          return "DVD+R Dual Layer";
623          break;
624       case 0x40:
625          return "BD-ROM";
626          break;
627       case 0x41:
628          return "BD-R SRM";
629          break;
630       case 0x42:
631          return "BD-R RRM";
632          break;
633       case 0x43:
634          return "BD-RE";
635          break;
636       case 0x50:
637          return "HD DVD-ROM";
638          break;
639       case 0x51:
640          return "HD DVD-R";
641          break;
642       case 0x52:
643          return "HD DVD-RAM";
644          break;
645       case 0x53:
646          return "HD DVD-RW";
647          break;
648       case 0x58:
649          return "HD DVD-R Dual Layer";
650          break;
651       case 0x5A:
652          return "HD DVD-RW Dual Layer";
653          break;
654       default:
655          break;
656    }
657 
658    return "Unknown";
659 }
660 
cdrom_get_sense(libretro_vfs_implementation_file * stream,unsigned char * sense,size_t len)661 int cdrom_get_sense(libretro_vfs_implementation_file *stream, unsigned char *sense, size_t len)
662 {
663    unsigned char cdb[] = {0x3, 0, 0, 0, 0xFC, 0};
664    unsigned char buf[0xFC] = {0};
665    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
666 
667 #ifdef CDROM_DEBUG
668    printf("[CDROM] get sense data status code %d\n", rv);
669    fflush(stdout);
670 #endif
671 
672    if (rv)
673       return 1;
674 
675    cdrom_print_sense_data(buf, sizeof(buf));
676 
677    return 0;
678 }
679 
cdrom_get_current_config_random_readable(libretro_vfs_implementation_file * stream)680 void cdrom_get_current_config_random_readable(libretro_vfs_implementation_file *stream)
681 {
682    unsigned char cdb[] = {0x46, 0x2, 0, 0x10, 0, 0, 0, 0, 0x14, 0};
683    unsigned char buf[0x14] = {0};
684    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
685    int i;
686 
687    printf("[CDROM] get current config random readable status code %d\n", rv);
688 
689    if (rv)
690       return;
691 
692    printf("[CDROM] Feature Header: ");
693 
694    for (i = 0; i < 8; i++)
695    {
696       printf("%02X ", buf[i]);
697    }
698 
699    printf("\n");
700 
701    printf("[CDROM] Random Readable Feature Descriptor: ");
702 
703    for (i = 0; i < 12; i++)
704    {
705       printf("%02X ", buf[8 + i]);
706    }
707 
708    printf("\n");
709 
710    printf("[CDROM] Supported commands: READ CAPACITY, READ (10)\n");
711 }
712 
cdrom_get_current_config_multiread(libretro_vfs_implementation_file * stream)713 void cdrom_get_current_config_multiread(libretro_vfs_implementation_file *stream)
714 {
715    unsigned char cdb[] = {0x46, 0x2, 0, 0x1D, 0, 0, 0, 0, 0xC, 0};
716    unsigned char buf[0xC] = {0};
717    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
718    int i;
719 
720    printf("[CDROM] get current config multi-read status code %d\n", rv);
721 
722    if (rv)
723       return;
724 
725    printf("[CDROM] Feature Header: ");
726 
727    for (i = 0; i < 8; i++)
728    {
729       printf("%02X ", buf[i]);
730    }
731 
732    printf("\n");
733 
734    printf("[CDROM] Multi-Read Feature Descriptor: ");
735 
736    for (i = 0; i < 4; i++)
737    {
738       printf("%02X ", buf[8 + i]);
739    }
740 
741    printf("\n");
742 
743    printf("[CDROM] Supported commands: READ (10), READ CD, READ DISC INFORMATION, READ TRACK INFORMATION\n");
744 }
745 
cdrom_get_current_config_cdread(libretro_vfs_implementation_file * stream)746 void cdrom_get_current_config_cdread(libretro_vfs_implementation_file *stream)
747 {
748    unsigned char cdb[] = {0x46, 0x2, 0, 0x1E, 0, 0, 0, 0, 0x10, 0};
749    unsigned char buf[0x10] = {0};
750    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
751    int i;
752 
753    printf("[CDROM] get current config cd read status code %d\n", rv);
754 
755    if (rv)
756       return;
757 
758    printf("[CDROM] Feature Header: ");
759 
760    for (i = 0; i < 8; i++)
761    {
762       printf("%02X ", buf[i]);
763    }
764 
765    printf("\n");
766 
767    printf("[CDROM] CD Read Feature Descriptor: ");
768 
769    for (i = 0; i < 8; i++)
770    {
771       printf("%02X ", buf[8 + i]);
772    }
773 
774    if (buf[8 + 2] & 1)
775       printf("(current)\n");
776 
777    printf("[CDROM] Supported commands: READ CD, READ CD MSF, READ TOC/PMA/ATIP\n");
778 }
779 
cdrom_get_current_config_profiles(libretro_vfs_implementation_file * stream)780 void cdrom_get_current_config_profiles(libretro_vfs_implementation_file *stream)
781 {
782    unsigned char cdb[] = {0x46, 0x2, 0, 0x0, 0, 0, 0, 0xFF, 0xFA, 0};
783    unsigned char buf[0xFFFA] = {0};
784    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
785    int i;
786 
787    printf("[CDROM] get current config profiles status code %d\n", rv);
788 
789    if (rv)
790       return;
791 
792    printf("[CDROM] Feature Header: ");
793 
794    for (i = 0; i < 8; i++)
795    {
796       printf("%02X ", buf[i]);
797    }
798 
799    printf("\n");
800 
801    printf("[CDROM] Profile List Descriptor: ");
802 
803    for (i = 0; i < 4; i++)
804    {
805       printf("%02X ", buf[8 + i]);
806    }
807 
808    printf("\n");
809 
810    printf("[CDROM] Number of profiles: %u\n", buf[8 + 3] / 4);
811 
812    for (i = 0; i < buf[8 + 3] / 4; i++)
813    {
814       unsigned short profile = (buf[8 + (4 * (i + 1))] << 8) | buf[8 + (4 * (i + 1)) + 1];
815 
816       printf("[CDROM] Profile Number: %04X (%s) ", profile, get_profile(profile));
817 
818       if (buf[8 + (4 * (i + 1)) + 2] & 1)
819          printf("(current)\n");
820       else
821          printf("\n");
822    }
823 }
824 
cdrom_get_current_config_core(libretro_vfs_implementation_file * stream)825 void cdrom_get_current_config_core(libretro_vfs_implementation_file *stream)
826 {
827    unsigned char cdb[] = {0x46, 0x2, 0, 0x1, 0, 0, 0, 0, 0x14, 0};
828    unsigned char buf[20] = {0};
829    unsigned intf_std = 0;
830    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
831    int i;
832    const char *intf_std_name = "Unknown";
833 
834    printf("[CDROM] get current config core status code %d\n", rv);
835 
836    if (rv)
837       return;
838 
839    printf("[CDROM] Feature Header: ");
840 
841    for (i = 0; i < 8; i++)
842    {
843       printf("%02X ", buf[i]);
844    }
845 
846    printf("\n");
847 
848    if (buf[6] == 0 && buf[7] == 8)
849       printf("[CDROM] Current Profile: CD-ROM\n");
850    else
851       printf("[CDROM] Current Profile: %02X%02X\n", buf[6], buf[7]);
852 
853    printf("[CDROM] Core Feature Descriptor: ");
854 
855    for (i = 0; i < 12; i++)
856    {
857       printf("%02X ", buf[8 + i]);
858    }
859 
860    printf("\n");
861 
862    intf_std = buf[8 + 4] << 24 | buf[8 + 5] << 16 | buf[8 + 6] << 8 | buf[8 + 7];
863 
864    switch (intf_std)
865    {
866       case 0:
867          intf_std_name = "Unspecified";
868          break;
869       case 1:
870          intf_std_name = "SCSI Family";
871          break;
872       case 2:
873          intf_std_name = "ATAPI";
874          break;
875       case 7:
876          intf_std_name = "Serial ATAPI";
877          break;
878       case 8:
879          intf_std_name = "USB";
880          break;
881       default:
882          break;
883    }
884 
885    printf("[CDROM] Physical Interface Standard: %u (%s)\n", intf_std, intf_std_name);
886 }
887 
cdrom_read_subq(libretro_vfs_implementation_file * stream,unsigned char * buf,size_t len)888 int cdrom_read_subq(libretro_vfs_implementation_file *stream, unsigned char *buf, size_t len)
889 {
890    /* MMC Command: READ TOC/PMA/ATIP */
891    unsigned char cdb[] = {0x43, 0x2, 0x2, 0, 0, 0, 0x1, 0x9, 0x30, 0};
892 #ifdef CDROM_DEBUG
893    unsigned short data_len = 0;
894    unsigned char first_session = 0;
895    unsigned char last_session = 0;
896    int i;
897 #endif
898    int rv;
899 
900    if (!buf)
901       return 1;
902 
903    rv = cdrom_send_command(stream, DIRECTION_IN, buf, len, cdb, sizeof(cdb), 0);
904 
905    if (rv)
906      return 1;
907 
908 #ifdef CDROM_DEBUG
909    data_len = buf[0] << 8 | buf[1];
910    first_session = buf[2];
911    last_session = buf[3];
912 
913    printf("[CDROM] Data Length: %d\n", data_len);
914    printf("[CDROM] First Session: %d\n", first_session);
915    printf("[CDROM] Last Session: %d\n", last_session);
916 
917    for (i = 0; i < (data_len - 2) / 11; i++)
918    {
919       unsigned char session_num = buf[4 + (i * 11) + 0];
920       unsigned char adr = (buf[4 + (i * 11) + 1] >> 4) & 0xF;
921       /*unsigned char control = buf[4 + (i * 11) + 1] & 0xF;*/
922       unsigned char tno = buf[4 + (i * 11) + 2];
923       unsigned char point = buf[4 + (i * 11) + 3];
924       unsigned char pmin = buf[4 + (i * 11) + 8];
925       unsigned char psec = buf[4 + (i * 11) + 9];
926       unsigned char pframe = buf[4 + (i * 11) + 10];
927 
928       /*printf("i %d control %d adr %d tno %d point %d: ", i, control, adr, tno, point);*/
929       /* why is control always 0? */
930 
931       if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point >= 1 && point <= 99)
932       {
933          printf("[CDROM] - Session#: %d TNO %d POINT %d ", session_num, tno, point);
934          printf("[CDROM] Track start time: (MSF %02u:%02u:%02u) ", (unsigned)pmin, (unsigned)psec, (unsigned)pframe);
935       }
936       else if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point == 0xA0)
937       {
938          printf("[CDROM] - Session#: %d TNO %d POINT %d ", session_num, tno, point);
939          printf("[CDROM] First Track Number: %d ", pmin);
940          printf("[CDROM] Disc Type: %d ", psec);
941       }
942       else if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point == 0xA1)
943       {
944          printf("[CDROM] - Session#: %d TNO %d POINT %d ", session_num, tno, point);
945          printf("[CDROM] Last Track Number: %d ", pmin);
946       }
947       else if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point == 0xA2)
948       {
949          printf("[CDROM] - Session#: %d TNO %d POINT %d ", session_num, tno, point);
950          printf("[CDROM] Lead-out runtime: (MSF %02u:%02u:%02u) ", (unsigned)pmin, (unsigned)psec, (unsigned)pframe);
951       }
952 
953       printf("\n");
954    }
955 
956    fflush(stdout);
957 #endif
958    return 0;
959 }
960 
cdrom_read_track_info(libretro_vfs_implementation_file * stream,unsigned char track,cdrom_toc_t * toc)961 static int cdrom_read_track_info(libretro_vfs_implementation_file *stream, unsigned char track, cdrom_toc_t *toc)
962 {
963    /* MMC Command: READ TRACK INFORMATION */
964    unsigned char cdb[] = {0x52, 0x1, 0, 0, 0, 0, 0, 0x1, 0x80, 0};
965    unsigned char buf[384] = {0};
966    unsigned lba = 0;
967    unsigned track_size = 0;
968    int rv;
969    ssize_t pregap_lba_len;
970 
971    cdb[5] = track;
972 
973    rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
974 
975    if (rv)
976      return 1;
977 
978    memcpy(&lba, buf + 8, 4);
979    memcpy(&track_size, buf + 24, 4);
980 
981    lba = swap_if_little32(lba);
982    track_size = swap_if_little32(track_size);
983 
984    /* lba_start may be earlier than the MSF start times seen in read_subq */
985    toc->track[track - 1].lba_start = lba;
986    toc->track[track - 1].track_size = track_size;
987 
988    pregap_lba_len = (toc->track[track - 1].audio ? 0 : (toc->track[track - 1].lba - toc->track[track - 1].lba_start));
989 
990    toc->track[track - 1].track_bytes = (track_size - pregap_lba_len) * 2352;
991    toc->track[track - 1].mode = buf[6] & 0xF;
992 
993 #ifdef CDROM_DEBUG
994    printf("[CDROM] Track %d Info: ", track);
995    printf("[CDROM] Copy: %d ", (buf[5] & 0x10) > 0);
996    printf("[CDROM] Data Mode: %d ", toc->track[track - 1].mode);
997    printf("[CDROM] LBA Start: %d (%d) ", lba, toc->track[track - 1].lba);
998    printf("[CDROM] Track Size: %d\n", track_size);
999    fflush(stdout);
1000 #endif
1001 
1002    return 0;
1003 }
1004 
cdrom_set_read_speed(libretro_vfs_implementation_file * stream,unsigned speed)1005 int cdrom_set_read_speed(libretro_vfs_implementation_file *stream, unsigned speed)
1006 {
1007    /* MMC Command: SET CD SPEED */
1008    unsigned char cmd[] = {0xBB, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 };
1009 
1010    cmd[2] = (speed >> 24) & 0xFF;
1011    cmd[3] = (speed >> 16) & 0xFF;
1012    cmd[4] = (speed >> 8) & 0xFF;
1013    cmd[5] = speed & 0xFF;
1014 
1015    return cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cmd, sizeof(cmd), 0);
1016 }
1017 
cdrom_write_cue(libretro_vfs_implementation_file * stream,char ** out_buf,size_t * out_len,char cdrom_drive,unsigned char * num_tracks,cdrom_toc_t * toc)1018 int cdrom_write_cue(libretro_vfs_implementation_file *stream, char **out_buf, size_t *out_len, char cdrom_drive, unsigned char *num_tracks, cdrom_toc_t *toc)
1019 {
1020    unsigned char buf[2352] = {0};
1021    unsigned short data_len = 0;
1022    size_t len = 0;
1023    size_t pos = 0;
1024    int rv = 0;
1025    int i;
1026 
1027    if (!out_buf || !out_len || !num_tracks || !toc)
1028    {
1029 #ifdef CDROM_DEBUG
1030       printf("[CDROM] Invalid buffer/length pointer for CDROM cue sheet\n");
1031       fflush(stdout);
1032 #endif
1033       return 1;
1034    }
1035 
1036    cdrom_set_read_speed(stream, 0xFFFFFFFF);
1037 
1038    rv = cdrom_read_subq(stream, buf, sizeof(buf));
1039 
1040    if (rv)
1041       return rv;
1042 
1043    data_len = buf[0] << 8 | buf[1];
1044 
1045    for (i = 0; i < (data_len - 2) / 11; i++)
1046    {
1047       unsigned char adr = (buf[4 + (i * 11) + 1] >> 4) & 0xF;
1048       unsigned char tno = buf[4 + (i * 11) + 2];
1049       unsigned char point = buf[4 + (i * 11) + 3];
1050       unsigned char pmin = buf[4 + (i * 11) + 8];
1051 
1052       if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point == 0xA1)
1053       {
1054          *num_tracks = pmin;
1055 #ifdef CDROM_DEBUG
1056          printf("[CDROM] Number of CDROM tracks: %d\n", *num_tracks);
1057          fflush(stdout);
1058 #endif
1059          break;
1060       }
1061    }
1062 
1063    if (!*num_tracks || *num_tracks > 99)
1064    {
1065 #ifdef CDROM_DEBUG
1066       printf("[CDROM] Invalid number of CDROM tracks: %d\n", *num_tracks);
1067       fflush(stdout);
1068 #endif
1069       return 1;
1070    }
1071 
1072    len = CDROM_CUE_TRACK_BYTES * (*num_tracks);
1073    toc->num_tracks = *num_tracks;
1074    *out_buf = (char*)calloc(1, len);
1075    *out_len = len;
1076 
1077    for (i = 0; i < (data_len - 2) / 11; i++)
1078    {
1079       /*unsigned char session_num = buf[4 + (i * 11) + 0];*/
1080       unsigned char adr = (buf[4 + (i * 11) + 1] >> 4) & 0xF;
1081       unsigned char control = buf[4 + (i * 11) + 1] & 0xF;
1082       unsigned char tno = buf[4 + (i * 11) + 2];
1083       unsigned char point = buf[4 + (i * 11) + 3];
1084       /*unsigned char amin = buf[4 + (i * 11) + 4];
1085       unsigned char asec = buf[4 + (i * 11) + 5];
1086       unsigned char aframe = buf[4 + (i * 11) + 6];*/
1087       unsigned char pmin = buf[4 + (i * 11) + 8];
1088       unsigned char psec = buf[4 + (i * 11) + 9];
1089       unsigned char pframe = buf[4 + (i * 11) + 10];
1090       unsigned lba = cdrom_msf_to_lba(pmin, psec, pframe);
1091 
1092       /*printf("i %d control %d adr %d tno %d point %d: amin %d asec %d aframe %d pmin %d psec %d pframe %d\n", i, control, adr, tno, point, amin, asec, aframe, pmin, psec, pframe);*/
1093       /* why is control always 0? */
1094 
1095       if (/*(control == 4 || control == 6) && */adr == 1 && tno == 0 && point >= 1 && point <= 99)
1096       {
1097          bool audio = false;
1098          const char *track_type = "MODE1/2352";
1099 
1100          audio = (!(control & 0x4) && !(control & 0x5));
1101 
1102 #ifdef CDROM_DEBUG
1103          printf("[CDROM] Track %02d CONTROL %01X ADR %01X AUDIO? %d\n", point, control, adr, audio);
1104          fflush(stdout);
1105 #endif
1106 
1107          toc->track[point - 1].track_num = point;
1108          toc->track[point - 1].min = pmin;
1109          toc->track[point - 1].sec = psec;
1110          toc->track[point - 1].frame = pframe;
1111          toc->track[point - 1].lba = lba;
1112          toc->track[point - 1].audio = audio;
1113 
1114          cdrom_read_track_info(stream, point, toc);
1115 
1116          if (audio)
1117             track_type = "AUDIO";
1118          else if (toc->track[point - 1].mode == 1)
1119             track_type = "MODE1/2352";
1120          else if (toc->track[point - 1].mode == 2)
1121             track_type = "MODE2/2352";
1122 
1123 #if defined(_WIN32) && !defined(_XBOX)
1124          pos += snprintf(*out_buf + pos, len - pos, "FILE \"cdrom://%c:/drive-track%02d.bin\" BINARY\n", cdrom_drive, point);
1125 #else
1126          pos += snprintf(*out_buf + pos, len - pos, "FILE \"cdrom://drive%c-track%02d.bin\" BINARY\n", cdrom_drive, point);
1127 #endif
1128          pos += snprintf(*out_buf + pos, len - pos, "  TRACK %02d %s\n", point, track_type);
1129 
1130          {
1131             unsigned pregap_lba_len = toc->track[point - 1].lba - toc->track[point - 1].lba_start;
1132 
1133             if (toc->track[point - 1].audio && pregap_lba_len > 0)
1134             {
1135                unsigned char min = 0;
1136                unsigned char sec = 0;
1137                unsigned char frame = 0;
1138 
1139                cdrom_lba_to_msf(pregap_lba_len, &min, &sec, &frame);
1140 
1141                pos += snprintf(*out_buf + pos, len - pos, "    INDEX 00 00:00:00\n");
1142                pos += snprintf(*out_buf + pos, len - pos, "    INDEX 01 %02u:%02u:%02u\n", (unsigned)min, (unsigned)sec, (unsigned)frame);
1143             }
1144             else
1145                pos += snprintf(*out_buf + pos, len - pos, "    INDEX 01 00:00:00\n");
1146          }
1147       }
1148    }
1149 
1150    return 0;
1151 }
1152 
1153 /* needs 32 bytes for full vendor, product and version */
cdrom_get_inquiry(libretro_vfs_implementation_file * stream,char * model,int len,bool * is_cdrom)1154 int cdrom_get_inquiry(libretro_vfs_implementation_file *stream, char *model, int len, bool *is_cdrom)
1155 {
1156    /* MMC Command: INQUIRY */
1157    unsigned char cdb[] = {0x12, 0, 0, 0, 0xff, 0};
1158    unsigned char buf[256] = {0};
1159    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
1160    bool cdrom = false;
1161 
1162    if (rv)
1163       return 1;
1164 
1165    if (model && len >= 32)
1166    {
1167       memset(model, 0, len);
1168 
1169       /* vendor */
1170       memcpy(model, buf + 8, 8);
1171 
1172       model[8] = ' ';
1173 
1174       /* product */
1175       memcpy(model + 9, buf + 16, 16);
1176 
1177       model[25] = ' ';
1178 
1179       /* version */
1180       memcpy(model + 26, buf + 32, 4);
1181    }
1182 
1183    cdrom = (buf[0] == 5);
1184 
1185    if (is_cdrom && cdrom)
1186       *is_cdrom = true;
1187 
1188 #ifdef CDROM_DEBUG
1189    printf("[CDROM] Device Model: %s (is CD-ROM? %s)\n", model, (cdrom ? "yes" : "no"));
1190 #endif
1191    return 0;
1192 }
1193 
cdrom_read(libretro_vfs_implementation_file * stream,cdrom_group_timeouts_t * timeouts,unsigned char min,unsigned char sec,unsigned char frame,void * s,size_t len,size_t skip)1194 int cdrom_read(libretro_vfs_implementation_file *stream, cdrom_group_timeouts_t *timeouts, unsigned char min, unsigned char sec, unsigned char frame, void *s, size_t len, size_t skip)
1195 {
1196    /* MMC Command: READ CD MSF */
1197    unsigned char cdb[] = {0xB9, 0, 0, 0, 0, 0, 0, 0, 0, 0xF8, 0, 0};
1198    int rv;
1199    double frames = ceil((len + skip) / 2352.0);
1200    unsigned frame_end = cdrom_msf_to_lba(min, sec, frame) + frames;
1201 
1202    cdb[3] = min;
1203    cdb[4] = sec;
1204    cdb[5] = frame;
1205 
1206    if (frames <= 1)
1207    {
1208       cdrom_lba_to_msf(frame_end, &cdb[6], &cdb[7], &cdb[8]);
1209 #ifdef CDROM_DEBUG
1210       printf("[CDROM] single-frame read: %d %d %d skip %" PRId64 "\n", cdb[3], cdb[4], cdb[5], skip);
1211       fflush(stdout);
1212 #endif
1213    }
1214    else
1215    {
1216       cdrom_lba_to_msf(frame_end, &cdb[6], &cdb[7], &cdb[8]);
1217 
1218 #ifdef CDROM_DEBUG
1219       printf("[CDROM] multi-frame read: %d sectors starting from %02d:%02d:%02d skip %" PRId64 "\n", (int)frames, cdb[3], cdb[4], cdb[5], skip);
1220       fflush(stdout);
1221 #endif
1222    }
1223 
1224    /* regardless of the length specified here, a new buffer will be allocated and padded to a sector multiple inside cdrom_send_command */
1225    rv = cdrom_send_command(stream, DIRECTION_IN, s, len, cdb, sizeof(cdb), skip);
1226 
1227 #ifdef CDROM_DEBUG
1228    printf("[CDROM] read msf status code %d\n", rv);
1229    fflush(stdout);
1230 #endif
1231 
1232    if (rv)
1233    {
1234       stream->cdrom.last_frame_valid = false;
1235       return 1;
1236    }
1237 
1238    return 0;
1239 }
1240 
cdrom_stop(libretro_vfs_implementation_file * stream)1241 int cdrom_stop(libretro_vfs_implementation_file *stream)
1242 {
1243    /* MMC Command: START STOP UNIT */
1244    unsigned char cdb[] = {0x1B, 0, 0, 0, 0x0, 0};
1245    int rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1246 
1247 #ifdef CDROM_DEBUG
1248    printf("[CDROM] stop status code %d\n", rv);
1249    fflush(stdout);
1250 #endif
1251 
1252    if (rv)
1253       return 1;
1254 
1255    return 0;
1256 }
1257 
cdrom_unlock(libretro_vfs_implementation_file * stream)1258 int cdrom_unlock(libretro_vfs_implementation_file *stream)
1259 {
1260    /* MMC Command: PREVENT ALLOW MEDIUM REMOVAL */
1261    unsigned char cdb[] = {0x1E, 0, 0, 0, 0x2, 0};
1262    int rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1263 
1264 #ifdef CDROM_DEBUG
1265    printf("[CDROM] persistent prevent clear status code %d\n", rv);
1266    fflush(stdout);
1267 #endif
1268 
1269    if (rv)
1270       return 1;
1271 
1272    cdb[4] = 0x0;
1273 
1274    rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1275 
1276 #ifdef CDROM_DEBUG
1277    printf("[CDROM] prevent clear status code %d\n", rv);
1278    fflush(stdout);
1279 #endif
1280 
1281    if (rv)
1282       return 1;
1283 
1284    return 0;
1285 }
1286 
cdrom_open_tray(libretro_vfs_implementation_file * stream)1287 int cdrom_open_tray(libretro_vfs_implementation_file *stream)
1288 {
1289    /* MMC Command: START STOP UNIT */
1290    unsigned char cdb[] = {0x1B, 0, 0, 0, 0x2, 0};
1291    int rv;
1292 
1293    cdrom_unlock(stream);
1294    cdrom_stop(stream);
1295 
1296    rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1297 
1298 #ifdef CDROM_DEBUG
1299    printf("[CDROM] open tray status code %d\n", rv);
1300    fflush(stdout);
1301 #endif
1302 
1303    if (rv)
1304       return 1;
1305 
1306    return 0;
1307 }
1308 
cdrom_close_tray(libretro_vfs_implementation_file * stream)1309 int cdrom_close_tray(libretro_vfs_implementation_file *stream)
1310 {
1311    /* MMC Command: START STOP UNIT */
1312    unsigned char cdb[] = {0x1B, 0, 0, 0, 0x3, 0};
1313    int rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1314 
1315 #ifdef CDROM_DEBUG
1316    printf("[CDROM] close tray status code %d\n", rv);
1317    fflush(stdout);
1318 #endif
1319 
1320    if (rv)
1321       return 1;
1322 
1323    return 0;
1324 }
1325 
cdrom_get_available_drives(void)1326 struct string_list* cdrom_get_available_drives(void)
1327 {
1328    struct string_list *list = string_list_new();
1329 #if defined(__linux__) && !defined(ANDROID)
1330    struct string_list *dir_list = dir_list_new("/dev", NULL, false, false, false, false);
1331    int i;
1332 
1333    if (!dir_list)
1334       return list;
1335 
1336    for (i = 0; i < (int)dir_list->size; i++)
1337    {
1338       if (strstr(dir_list->elems[i].data, "/dev/sg"))
1339       {
1340          char drive_model[32] = {0};
1341          char drive_string[33] = {0};
1342          union string_list_elem_attr attr = {0};
1343          int dev_index = 0;
1344          RFILE *file = filestream_open(dir_list->elems[i].data, RETRO_VFS_FILE_ACCESS_READ, 0);
1345          libretro_vfs_implementation_file *stream;
1346          bool is_cdrom = false;
1347 
1348          if (!file)
1349             continue;
1350 
1351          stream = filestream_get_vfs_handle(file);
1352          cdrom_get_inquiry(stream, drive_model, sizeof(drive_model), &is_cdrom);
1353          filestream_close(file);
1354 
1355          if (!is_cdrom)
1356             continue;
1357 
1358          sscanf(dir_list->elems[i].data + strlen("/dev/sg"), "%d", &dev_index);
1359 
1360          dev_index = '0' + dev_index;
1361          attr.i = dev_index;
1362 
1363          if (!string_is_empty(drive_model))
1364             strlcat(drive_string, drive_model, sizeof(drive_string));
1365          else
1366             strlcat(drive_string, "Unknown Drive", sizeof(drive_string));
1367 
1368          string_list_append(list, drive_string, attr);
1369       }
1370    }
1371 
1372    string_list_free(dir_list);
1373 #endif
1374 #if defined(_WIN32) && !defined(_XBOX)
1375    DWORD drive_mask = GetLogicalDrives();
1376    int i;
1377 
1378    for (i = 0; i < sizeof(DWORD) * 8; i++)
1379    {
1380       char path[] = {"a:\\"};
1381       char cdrom_path[] = {"cdrom://a:/drive-track01.bin"};
1382 
1383       path[0] += i;
1384       cdrom_path[8] += i;
1385 
1386       /* this drive letter doesn't exist */
1387       if (!(drive_mask & (1 << i)))
1388          continue;
1389 
1390       if (GetDriveType(path) != DRIVE_CDROM)
1391          continue;
1392       else
1393       {
1394          char drive_model[32] = {0};
1395          char drive_string[33] = {0};
1396          union string_list_elem_attr attr = {0};
1397          RFILE *file = filestream_open(cdrom_path, RETRO_VFS_FILE_ACCESS_READ, 0);
1398          libretro_vfs_implementation_file *stream;
1399          bool is_cdrom = false;
1400 
1401          if (!file)
1402             continue;
1403 
1404          stream = filestream_get_vfs_handle(file);
1405          cdrom_get_inquiry(stream, drive_model, sizeof(drive_model), &is_cdrom);
1406          filestream_close(file);
1407 
1408          if (!is_cdrom)
1409             continue;
1410 
1411          attr.i = path[0];
1412 
1413          if (!string_is_empty(drive_model))
1414             strlcat(drive_string, drive_model, sizeof(drive_string));
1415          else
1416             strlcat(drive_string, "Unknown Drive", sizeof(drive_string));
1417 
1418          string_list_append(list, drive_string, attr);
1419       }
1420    }
1421 #endif
1422    return list;
1423 }
1424 
cdrom_is_media_inserted(libretro_vfs_implementation_file * stream)1425 bool cdrom_is_media_inserted(libretro_vfs_implementation_file *stream)
1426 {
1427    /* MMC Command: TEST UNIT READY */
1428    unsigned char cdb[] = {0x00, 0, 0, 0, 0, 0};
1429    int rv = cdrom_send_command(stream, DIRECTION_NONE, NULL, 0, cdb, sizeof(cdb), 0);
1430 
1431 #ifdef CDROM_DEBUG
1432    printf("[CDROM] media inserted status code %d\n", rv);
1433    fflush(stdout);
1434 #endif
1435 
1436    /* Will also return false if the drive is simply not ready yet (tray open, disc spinning back up after tray closed etc).
1437     * Command will not block or wait for media to become ready. */
1438    if (rv)
1439       return false;
1440 
1441    return true;
1442 }
1443 
cdrom_drive_has_media(const char drive)1444 bool cdrom_drive_has_media(const char drive)
1445 {
1446    RFILE *file;
1447    char cdrom_path_bin[256];
1448 
1449    cdrom_path_bin[0] = '\0';
1450 
1451    cdrom_device_fillpath(cdrom_path_bin, sizeof(cdrom_path_bin), drive, 1, false);
1452 
1453    file = filestream_open(cdrom_path_bin, RETRO_VFS_FILE_ACCESS_READ, 0);
1454 
1455    if (file)
1456    {
1457       libretro_vfs_implementation_file *stream = filestream_get_vfs_handle(file);
1458       bool has_media = false;
1459 
1460       has_media = cdrom_is_media_inserted(stream);
1461 
1462       filestream_close(file);
1463 
1464       return has_media;
1465    }
1466 
1467    return false;
1468 }
1469 
cdrom_set_read_cache(libretro_vfs_implementation_file * stream,bool enabled)1470 bool cdrom_set_read_cache(libretro_vfs_implementation_file *stream, bool enabled)
1471 {
1472    /* MMC Command: MODE SENSE (10) and MODE SELECT (10) */
1473    unsigned char cdb_sense_changeable[] = {0x5A, 0, 0x48, 0, 0, 0, 0, 0, 0x14, 0};
1474    unsigned char cdb_sense[] = {0x5A, 0, 0x8, 0, 0, 0, 0, 0, 0x14, 0};
1475    unsigned char cdb_select[] = {0x55, 0x10, 0, 0, 0, 0, 0, 0, 0x14, 0};
1476    unsigned char buf[20] = {0};
1477    int rv, i;
1478 
1479    rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb_sense_changeable, sizeof(cdb_sense_changeable), 0);
1480 
1481 #ifdef CDROM_DEBUG
1482    printf("[CDROM] mode sense changeable status code %d\n", rv);
1483    fflush(stdout);
1484 #endif
1485 
1486    if (rv)
1487       return false;
1488 
1489    if (!(buf[10] & 0x1))
1490    {
1491       /* RCD (read cache disable) bit is not changeable */
1492 #ifdef CDROM_DEBUG
1493       printf("[CDROM] RCD (read cache disable) bit is not changeable.\n");
1494       fflush(stdout);
1495 #endif
1496       return false;
1497    }
1498 
1499    memset(buf, 0, sizeof(buf));
1500 
1501    rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb_sense, sizeof(cdb_sense), 0);
1502 
1503 #ifdef CDROM_DEBUG
1504    printf("mode sense status code %d\n", rv);
1505    fflush(stdout);
1506 #endif
1507 
1508    if (rv)
1509       return false;
1510 
1511 #ifdef CDROM_DEBUG
1512    printf("Mode sense data for caching mode page: ");
1513 
1514    for (i = 0; i < (int)sizeof(buf); i++)
1515    {
1516       printf("%02X ", buf[i]);
1517    }
1518 
1519    printf("\n");
1520    fflush(stdout);
1521 #endif
1522 
1523    /* "When transferred during execution of the MODE SELECT (10) command, Mode Data Length is reserved." */
1524    for (i = 0; i < 8; i++)
1525       buf[i] = 0;
1526 
1527    if (enabled)
1528       buf[10] &= ~1;
1529    else
1530       buf[10] |= 1;
1531 
1532    rv = cdrom_send_command(stream, DIRECTION_OUT, buf, sizeof(buf), cdb_select, sizeof(cdb_select), 0);
1533 
1534 #ifdef CDROM_DEBUG
1535    printf("mode select status code %d\n", rv);
1536    fflush(stdout);
1537 #endif
1538 
1539    if (rv)
1540       return false;
1541 
1542    return true;
1543 }
1544 
cdrom_get_timeouts(libretro_vfs_implementation_file * stream,cdrom_group_timeouts_t * timeouts)1545 bool cdrom_get_timeouts(libretro_vfs_implementation_file *stream, cdrom_group_timeouts_t *timeouts)
1546 {
1547    /* MMC Command: MODE SENSE (10) */
1548    int rv;
1549    unsigned char cdb[]   = {0x5A, 0, 0x1D, 0, 0, 0, 0, 0, 0x14, 0};
1550    unsigned char buf[20] = {0};
1551    unsigned short g1     = 0;
1552    unsigned short g2     = 0;
1553    unsigned short g3     = 0;
1554 
1555    if (!timeouts)
1556       return false;
1557 
1558    rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
1559 
1560 #ifdef CDROM_DEBUG
1561    printf("get timeouts status code %d\n", rv);
1562    fflush(stdout);
1563 #endif
1564 
1565    if (rv)
1566       return false;
1567 
1568    g1 = buf[14] << 8 | buf[15];
1569    g2 = buf[16] << 8 | buf[17];
1570    g3 = buf[18] << 8 | buf[19];
1571 
1572 #ifdef CDROM_DEBUG
1573    {
1574       int i;
1575 
1576       printf("Mode sense data for timeout groups: ");
1577 
1578       for (i = 0; i < (int)sizeof(buf); i++)
1579       {
1580          printf("%02X ", buf[i]);
1581       }
1582 
1583       printf("\n");
1584 
1585       printf("Group 1 Timeout: %d\n", g1);
1586       printf("Group 2 Timeout: %d\n", g2);
1587       printf("Group 3 Timeout: %d\n", g3);
1588 
1589       fflush(stdout);
1590    }
1591 #endif
1592 
1593    timeouts->g1_timeout = g1;
1594    timeouts->g2_timeout = g2;
1595    timeouts->g3_timeout = g3;
1596 
1597    return true;
1598 }
1599 
cdrom_has_atip(libretro_vfs_implementation_file * stream)1600 bool cdrom_has_atip(libretro_vfs_implementation_file *stream)
1601 {
1602    /* MMC Command: READ TOC/PMA/ATIP */
1603    unsigned char cdb[] = {0x43, 0x2, 0x4, 0, 0, 0, 0, 0x9, 0x30, 0};
1604    unsigned char buf[32] = {0};
1605    unsigned short atip_len = 0;
1606    int rv = cdrom_send_command(stream, DIRECTION_IN, buf, sizeof(buf), cdb, sizeof(cdb), 0);
1607 
1608    if (rv)
1609      return false;
1610 
1611    atip_len = buf[0] << 8 | buf[1];
1612 
1613 #ifdef CDROM_DEBUG
1614    printf("ATIP Length %d, Disc Type %d, Disc Sub-Type %d\n", atip_len, (buf[6] >> 6) & 0x1, ((buf[6] >> 5) & 0x1) << 2 | ((buf[6] >> 4) & 0x1) << 1 | ((buf[6] >> 3) & 0x1) << 0);
1615 #endif
1616 
1617    if (atip_len < 5)
1618       return false;
1619 
1620    return true;
1621 }
1622 
cdrom_device_fillpath(char * path,size_t len,char drive,unsigned char track,bool is_cue)1623 void cdrom_device_fillpath(char *path, size_t len, char drive, unsigned char track, bool is_cue)
1624 {
1625    size_t pos = 0;
1626 
1627    if (!path || len == 0)
1628       return;
1629 
1630    if (is_cue)
1631    {
1632 #ifdef _WIN32
1633       pos = strlcpy(path, "cdrom://", len);
1634 
1635       if (len > pos)
1636          path[pos++] = drive;
1637 
1638       pos = strlcat(path, ":/drive.cue", len);
1639 #else
1640 #ifdef __linux__
1641       pos = strlcpy(path, "cdrom://drive", len);
1642 
1643       if (len > pos)
1644          path[pos++] = drive;
1645 
1646       pos = strlcat(path, ".cue", len);
1647 #endif
1648 #endif
1649    }
1650    else
1651    {
1652 #ifdef _WIN32
1653       pos = strlcpy(path, "cdrom://", len);
1654 
1655       if (len > pos)
1656          path[pos++] = drive;
1657 
1658       pos += snprintf(path + pos, len - pos, ":/drive-track%02d.bin", track);
1659 #else
1660 #ifdef __linux__
1661       pos = strlcpy(path, "cdrom://drive", len);
1662 
1663       if (len > pos)
1664          path[pos++] = drive;
1665 
1666       pos += snprintf(path + pos, len - pos, "-track%02d.bin", track);
1667 #endif
1668 #endif
1669    }
1670 }
1671