1 /* Copyright  (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (vfs_implementation_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 #include <vfs/vfs_implementation.h>
24 #include <file/file_path.h>
25 #include <compat/fopen_utf8.h>
26 #include <string/stdstring.h>
27 #include <cdrom/cdrom.h>
28 
29 #if defined(_WIN32) && !defined(_XBOX)
30 #include <windows.h>
31 #endif
32 
33 /* TODO/FIXME - static global variable */
34 static cdrom_toc_t vfs_cdrom_toc = {0};
35 
retro_vfs_file_get_cdrom_toc(void)36 const cdrom_toc_t* retro_vfs_file_get_cdrom_toc(void)
37 {
38    return &vfs_cdrom_toc;
39 }
40 
retro_vfs_file_seek_cdrom(libretro_vfs_implementation_file * stream,int64_t offset,int whence)41 int64_t retro_vfs_file_seek_cdrom(
42       libretro_vfs_implementation_file *stream,
43       int64_t offset, int whence)
44 {
45    const char *ext = path_get_extension(stream->orig_path);
46 
47    if (string_is_equal_noncase(ext, "cue"))
48    {
49       switch (whence)
50       {
51          case SEEK_SET:
52             stream->cdrom.byte_pos = offset;
53             break;
54          case SEEK_CUR:
55             stream->cdrom.byte_pos += offset;
56             break;
57          case SEEK_END:
58             stream->cdrom.byte_pos  = (stream->cdrom.cue_len - 1) + offset;
59             break;
60       }
61 
62 #ifdef CDROM_DEBUG
63       printf("[CDROM] Seek: Path %s Offset %" PRIu64 " is now at %" PRIu64 "\n",
64             stream->orig_path,
65             offset,
66             stream->cdrom.byte_pos);
67       fflush(stdout);
68 #endif
69    }
70    else if (string_is_equal_noncase(ext, "bin"))
71    {
72       int lba               = (offset / 2352);
73       unsigned char min     = 0;
74       unsigned char sec     = 0;
75       unsigned char frame   = 0;
76 #ifdef CDROM_DEBUG
77       const char *seek_type = "SEEK_SET";
78 #endif
79 
80       switch (whence)
81       {
82          case SEEK_CUR:
83             {
84                unsigned new_lba;
85 #ifdef CDROM_DEBUG
86                seek_type               = "SEEK_CUR";
87 #endif
88                stream->cdrom.byte_pos += offset;
89                new_lba                 = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352);
90 
91                cdrom_lba_to_msf(new_lba, &min, &sec, &frame);
92             }
93             break;
94          case SEEK_END:
95             {
96                ssize_t pregap_lba_len = (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].audio
97                      ? 0
98                      : (vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba_start));
99                ssize_t lba_len        = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_size - pregap_lba_len;
100 #ifdef CDROM_DEBUG
101                seek_type              = "SEEK_END";
102 #endif
103 
104                cdrom_lba_to_msf(lba_len + lba, &min, &sec, &frame);
105                stream->cdrom.byte_pos = lba_len * 2352;
106             }
107             break;
108          case SEEK_SET:
109          default:
110             {
111 #ifdef CDROM_DEBUG
112                seek_type = "SEEK_SET";
113 #endif
114                stream->cdrom.byte_pos = offset;
115                cdrom_lba_to_msf(vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba + (stream->cdrom.byte_pos / 2352), &min, &sec, &frame);
116             }
117             break;
118       }
119 
120       stream->cdrom.cur_min   = min;
121       stream->cdrom.cur_sec   = sec;
122       stream->cdrom.cur_frame = frame;
123       stream->cdrom.cur_lba   = cdrom_msf_to_lba(min, sec, frame);
124 
125 #ifdef CDROM_DEBUG
126       printf(
127             "[CDROM] Seek %s: Path %s Offset %" PRIu64 " is now at %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)...\n",
128             seek_type,
129             stream->orig_path,
130             offset,
131             stream->cdrom.byte_pos,
132             (unsigned)stream->cdrom.cur_min,
133             (unsigned)stream->cdrom.cur_sec,
134             (unsigned)stream->cdrom.cur_frame,
135             stream->cdrom.cur_lba);
136       fflush(stdout);
137 #endif
138    }
139    else
140       return -1;
141 
142    return 0;
143 }
144 
retro_vfs_file_open_cdrom(libretro_vfs_implementation_file * stream,const char * path,unsigned mode,unsigned hints)145 void retro_vfs_file_open_cdrom(
146       libretro_vfs_implementation_file *stream,
147       const char *path, unsigned mode, unsigned hints)
148 {
149 #if defined(__linux__) && !defined(ANDROID)
150    char cdrom_path[]       = "/dev/sg1";
151    size_t path_len         = strlen(path);
152    const char *ext         = path_get_extension(path);
153 
154    stream->cdrom.cur_track = 1;
155 
156    if (     !string_is_equal_noncase(ext, "cue")
157          && !string_is_equal_noncase(ext, "bin"))
158       return;
159 
160    if (path_len >= STRLEN_CONST("drive1-track01.bin"))
161    {
162       if (!memcmp(path, "drive", STRLEN_CONST("drive")))
163       {
164          if (!memcmp(path + 6, "-track", STRLEN_CONST("-track")))
165          {
166             if (sscanf(path + 12, "%02u", (unsigned*)&stream->cdrom.cur_track))
167             {
168 #ifdef CDROM_DEBUG
169                printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track);
170                fflush(stdout);
171 #endif
172             }
173          }
174       }
175    }
176 
177    if (path_len >= STRLEN_CONST("drive1.cue"))
178    {
179       if (!memcmp(path, "drive", STRLEN_CONST("drive")))
180       {
181          if (path[5] >= '0' && path[5] <= '9')
182          {
183             cdrom_path[7]       = path[5];
184             stream->cdrom.drive = path[5];
185             vfs_cdrom_toc.drive = stream->cdrom.drive;
186          }
187       }
188    }
189 
190 #ifdef CDROM_DEBUG
191    printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path);
192    fflush(stdout);
193 #endif
194    stream->fp = (FILE*)fopen_utf8(cdrom_path, "r+b");
195 
196    if (!stream->fp)
197       return;
198 
199    if (string_is_equal_noncase(ext, "cue"))
200    {
201       if (stream->cdrom.cue_buf)
202       {
203          free(stream->cdrom.cue_buf);
204          stream->cdrom.cue_buf = NULL;
205       }
206 
207       cdrom_write_cue(stream,
208             &stream->cdrom.cue_buf,
209             &stream->cdrom.cue_len,
210             stream->cdrom.drive,
211             &vfs_cdrom_toc.num_tracks,
212             &vfs_cdrom_toc);
213       cdrom_get_timeouts(stream, &vfs_cdrom_toc.timeouts);
214 
215 #ifdef CDROM_DEBUG
216       if (string_is_empty(stream->cdrom.cue_buf))
217       {
218          printf("[CDROM] Error writing cue sheet.\n");
219          fflush(stdout);
220       }
221       else
222       {
223          printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf);
224          fflush(stdout);
225       }
226 #endif
227    }
228 #endif
229 #if defined(_WIN32) && !defined(_XBOX)
230    char cdrom_path[] = "\\\\.\\D:";
231    size_t path_len   = strlen(path);
232    const char *ext   = path_get_extension(path);
233 
234    if (     !string_is_equal_noncase(ext, "cue")
235          && !string_is_equal_noncase(ext, "bin"))
236       return;
237 
238    if (path_len >= STRLEN_CONST("d:/drive-track01.bin"))
239    {
240       if (!memcmp(path + 1, ":/drive-track", STRLEN_CONST(":/drive-track")))
241       {
242          if (sscanf(path + 14, "%02u", (unsigned*)&stream->cdrom.cur_track))
243          {
244 #ifdef CDROM_DEBUG
245             printf("[CDROM] Opening track %d\n", stream->cdrom.cur_track);
246             fflush(stdout);
247 #endif
248          }
249       }
250    }
251 
252    if (path_len >= STRLEN_CONST("d:/drive.cue"))
253    {
254       if (!memcmp(path + 1, ":/drive", STRLEN_CONST(":/drive")))
255       {
256          if ((path[0] >= 'A' && path[0] <= 'Z') || (path[0] >= 'a' && path[0] <= 'z'))
257          {
258             cdrom_path[4]       = path[0];
259             stream->cdrom.drive = path[0];
260             vfs_cdrom_toc.drive = stream->cdrom.drive;
261          }
262       }
263    }
264 
265 #ifdef CDROM_DEBUG
266    printf("[CDROM] Open: Path %s URI %s\n", cdrom_path, path);
267    fflush(stdout);
268 #endif
269    stream->fh = CreateFile(cdrom_path,
270          GENERIC_READ | GENERIC_WRITE,
271          FILE_SHARE_READ | FILE_SHARE_WRITE,
272          NULL,
273          OPEN_EXISTING,
274          FILE_ATTRIBUTE_NORMAL,
275          NULL);
276 
277    if (stream->fh == INVALID_HANDLE_VALUE)
278       return;
279 
280    if (string_is_equal_noncase(ext, "cue"))
281    {
282       if (stream->cdrom.cue_buf)
283       {
284          free(stream->cdrom.cue_buf);
285          stream->cdrom.cue_buf = NULL;
286       }
287 
288       cdrom_write_cue(stream,
289             &stream->cdrom.cue_buf,
290             &stream->cdrom.cue_len,
291             stream->cdrom.drive,
292             &vfs_cdrom_toc.num_tracks,
293             &vfs_cdrom_toc);
294       cdrom_get_timeouts(stream,
295             &vfs_cdrom_toc.timeouts);
296 
297 #ifdef CDROM_DEBUG
298       if (string_is_empty(stream->cdrom.cue_buf))
299       {
300          printf("[CDROM] Error writing cue sheet.\n");
301          fflush(stdout);
302       }
303       else
304       {
305          printf("[CDROM] CUE Sheet:\n%s\n", stream->cdrom.cue_buf);
306          fflush(stdout);
307       }
308 #endif
309    }
310 #endif
311    if (vfs_cdrom_toc.num_tracks > 1 && stream->cdrom.cur_track)
312    {
313       stream->cdrom.cur_min   = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].min;
314       stream->cdrom.cur_sec   = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].sec;
315       stream->cdrom.cur_frame = vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].frame;
316       stream->cdrom.cur_lba   = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame);
317    }
318    else
319    {
320       stream->cdrom.cur_min   = vfs_cdrom_toc.track[0].min;
321       stream->cdrom.cur_sec   = vfs_cdrom_toc.track[0].sec;
322       stream->cdrom.cur_frame = vfs_cdrom_toc.track[0].frame;
323       stream->cdrom.cur_lba   = cdrom_msf_to_lba(stream->cdrom.cur_min, stream->cdrom.cur_sec, stream->cdrom.cur_frame);
324    }
325 }
326 
retro_vfs_file_close_cdrom(libretro_vfs_implementation_file * stream)327 int retro_vfs_file_close_cdrom(libretro_vfs_implementation_file *stream)
328 {
329 #ifdef CDROM_DEBUG
330    printf("[CDROM] Close: Path %s\n", stream->orig_path);
331    fflush(stdout);
332 #endif
333 
334 #if defined(_WIN32) && !defined(_XBOX)
335    if (!stream->fh || !CloseHandle(stream->fh))
336       return -1;
337 #else
338    if (!stream->fp || fclose(stream->fp))
339       return -1;
340 #endif
341 
342    return 0;
343 }
344 
retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file * stream)345 int64_t retro_vfs_file_tell_cdrom(libretro_vfs_implementation_file *stream)
346 {
347    const char *ext = NULL;
348    if (!stream)
349       return -1;
350 
351    ext = path_get_extension(stream->orig_path);
352 
353    if (string_is_equal_noncase(ext, "cue"))
354    {
355 #ifdef CDROM_DEBUG
356       printf("[CDROM] (cue) Tell: Path %s Position %" PRIu64 "\n", stream->orig_path, stream->cdrom.byte_pos);
357       fflush(stdout);
358 #endif
359       return stream->cdrom.byte_pos;
360    }
361    else if (string_is_equal_noncase(ext, "bin"))
362    {
363 #ifdef CDROM_DEBUG
364       printf("[CDROM] (bin) Tell: Path %s Position %" PRId64 "\n", stream->orig_path, stream->cdrom.byte_pos);
365       fflush(stdout);
366 #endif
367       return stream->cdrom.byte_pos;
368    }
369 
370    return -1;
371 }
372 
retro_vfs_file_read_cdrom(libretro_vfs_implementation_file * stream,void * s,uint64_t len)373 int64_t retro_vfs_file_read_cdrom(libretro_vfs_implementation_file *stream,
374       void *s, uint64_t len)
375 {
376    int rv;
377    const char *ext = path_get_extension(stream->orig_path);
378 
379    if (string_is_equal_noncase(ext, "cue"))
380    {
381       if ((int64_t)len >= (int64_t)stream->cdrom.cue_len
382             - stream->cdrom.byte_pos)
383          len = stream->cdrom.cue_len - stream->cdrom.byte_pos - 1;
384 #ifdef CDROM_DEBUG
385       printf(
386             "[CDROM] Read: Reading %" PRIu64 " bytes from cuesheet starting at %" PRIu64 "...\n",
387             len,
388             stream->cdrom.byte_pos);
389       fflush(stdout);
390 #endif
391       memcpy(s, stream->cdrom.cue_buf + stream->cdrom.byte_pos, len);
392       stream->cdrom.byte_pos += len;
393 
394       return len;
395    }
396    else if (string_is_equal_noncase(ext, "bin"))
397    {
398       unsigned char min    = 0;
399       unsigned char sec    = 0;
400       unsigned char frame  = 0;
401       unsigned char rmin   = 0;
402       unsigned char rsec   = 0;
403       unsigned char rframe = 0;
404       size_t skip          = stream->cdrom.byte_pos % 2352;
405 
406       if (stream->cdrom.byte_pos >=
407             vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes)
408          return 0;
409 
410       if (stream->cdrom.byte_pos + len >
411             vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes)
412          len -= (stream->cdrom.byte_pos + len)
413             - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].track_bytes;
414 
415       cdrom_lba_to_msf(stream->cdrom.cur_lba, &min, &sec, &frame);
416       cdrom_lba_to_msf(stream->cdrom.cur_lba
417             - vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba,
418             &rmin, &rsec, &rframe);
419 
420 #ifdef CDROM_DEBUG
421       printf(
422             "[CDROM] Read: Reading %" PRIu64 " bytes from %s starting at byte offset %" PRIu64 " (rMSF %02u:%02u:%02u aMSF %02u:%02u:%02u) (LBA %u) skip %" PRIu64 "...\n",
423             len,
424             stream->orig_path,
425             stream->cdrom.byte_pos,
426             (unsigned)rmin,
427             (unsigned)rsec,
428             (unsigned)rframe,
429             (unsigned)min,
430             (unsigned)sec,
431             (unsigned)frame,
432             stream->cdrom.cur_lba,
433             skip);
434       fflush(stdout);
435 #endif
436 
437 #if 1
438       rv = cdrom_read(stream, &vfs_cdrom_toc.timeouts, min, sec,
439             frame, s, (size_t)len, skip);
440 #else
441       rv = cdrom_read_lba(stream, stream->cdrom.cur_lba, s,
442             (size_t)len, skip);
443 #endif
444 
445       if (rv)
446       {
447 #ifdef CDROM_DEBUG
448          printf("[CDROM] Failed to read %" PRIu64 " bytes from CD.\n", len);
449          fflush(stdout);
450 #endif
451          return 0;
452       }
453 
454       stream->cdrom.byte_pos += len;
455       stream->cdrom.cur_lba   =
456          vfs_cdrom_toc.track[stream->cdrom.cur_track - 1].lba
457          + (stream->cdrom.byte_pos / 2352);
458 
459       cdrom_lba_to_msf(stream->cdrom.cur_lba,
460             &stream->cdrom.cur_min,
461             &stream->cdrom.cur_sec,
462             &stream->cdrom.cur_frame);
463 
464 #ifdef CDROM_DEBUG
465       printf(
466             "[CDROM] read %" PRIu64 " bytes, position is now: %" PRIu64 " (MSF %02u:%02u:%02u) (LBA %u)\n",
467             len,
468             stream->cdrom.byte_pos,
469             (unsigned)stream->cdrom.cur_min,
470             (unsigned)stream->cdrom.cur_sec,
471             (unsigned)stream->cdrom.cur_frame,
472             cdrom_msf_to_lba(
473                stream->cdrom.cur_min,
474                stream->cdrom.cur_sec,
475                stream->cdrom.cur_frame)
476             );
477       fflush(stdout);
478 #endif
479 
480       return len;
481    }
482 
483    return 0;
484 }
485 
retro_vfs_file_error_cdrom(libretro_vfs_implementation_file * stream)486 int retro_vfs_file_error_cdrom(libretro_vfs_implementation_file *stream)
487 {
488    return 0;
489 }
490 
retro_vfs_file_get_cdrom_position(const libretro_vfs_implementation_file * stream)491 const vfs_cdrom_t* retro_vfs_file_get_cdrom_position(
492       const libretro_vfs_implementation_file *stream)
493 {
494    return &stream->cdrom;
495 }
496