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