db: Override cycle multiplier for Colin McRae PAL
[pcsx_rearmed.git] / deps / libretro-common / media / media_detect_cd.c
1 /* Copyright  (C) 2010-2020 The RetroArch team
2 *
3 * ---------------------------------------------------------------------------------------
4 * The following license statement only applies to this file (media_detect_cd.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 <media/media_detect_cd.h>
24 #include <streams/file_stream.h>
25 #include <string/stdstring.h>
26 #include <file/file_path.h>
27 #include <retro_miscellaneous.h>
28
29 /*#define MEDIA_CUE_PARSE_DEBUG*/
30
31 static void media_zero_trailing_spaces(char *buf, size_t len)
32 {
33    int i;
34
35    for (i = len - 1; i >= 0; i--)
36    {
37       if (buf[i] == ' ')
38          buf[i] = '\0';
39       else if (buf[i] != '\0')
40          break;
41    }
42 }
43
44 static bool media_skip_spaces(const char **buf, size_t len)
45 {
46    bool found = false;
47    unsigned i;
48
49    if (!buf || !*buf || !**buf)
50       return false;
51
52    for (i = 0; i < len; i++)
53    {
54       if ((*buf)[i] == ' ' || (*buf)[i] == '\t')
55          continue;
56
57       *buf += i;
58       found = true;
59       break;
60    }
61
62    if (found)
63       return true;
64
65    return false;
66 }
67
68 /* Fill in "info" with detected CD info. Use this when you have a cue file and want it parsed to find the first data track and any pregap info. */
69 bool media_detect_cd_info_cue(const char *path, media_detect_cd_info_t *info)
70 {
71    RFILE *file = NULL;
72    char *line = NULL;
73    char track_path[PATH_MAX_LENGTH] = {0};
74    char track_abs_path[PATH_MAX_LENGTH] = {0};
75    char track_mode[11] = {0};
76    bool found_file = false;
77    bool found_track = false;
78    unsigned first_data_track = 0;
79    uint64_t data_track_pregap_bytes = 0;
80
81    if (string_is_empty(path) || !info)
82       return false;
83
84    file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0);
85
86    if (!file)
87    {
88 #ifdef MEDIA_CUE_PARSE_DEBUG
89       printf("[MEDIA] Could not open cue path for reading: %s\n", path);
90       fflush(stdout);
91 #endif
92       return false;
93    }
94
95    while (!filestream_eof(file) && (line = filestream_getline(file)))
96    {
97       size_t len = 0;
98       const char *command = NULL;
99
100       if (string_is_empty(line))
101       {
102          free(line);
103          continue;
104       }
105
106       len     = strlen(line);
107       command = line;
108
109       media_skip_spaces(&command, len);
110
111       if (!found_file && !strncasecmp(command, "FILE", 4))
112       {
113          const char *file = command + 4;
114          media_skip_spaces(&file, len - 4);
115
116          if (!string_is_empty(file))
117          {
118             const char *file_end = NULL;
119             size_t file_len = 0;
120             bool quoted = false;
121
122             if (file[0] == '"')
123             {
124                quoted = true;
125                file++;
126             }
127
128             if (quoted)
129                file_end = strchr(file, '\"');
130             else
131                file_end = strchr(file, ' ');
132
133             if (file_end)
134             {
135                file_len = file_end - file;
136                memcpy(track_path, file, file_len);
137                found_file = true;
138 #ifdef MEDIA_CUE_PARSE_DEBUG
139                printf("Found file: %s\n", track_path);
140                fflush(stdout);
141 #endif
142             }
143          }
144       }
145       else if (found_file && !found_track && !strncasecmp(command, "TRACK", 5))
146       {
147          const char *track = command + 5;
148          media_skip_spaces(&track, len - 5);
149
150          if (!string_is_empty(track))
151          {
152             char *ptr             = NULL;
153             unsigned track_number = (unsigned)strtol(track, &ptr, 10);
154 #ifdef MEDIA_CUE_PARSE_DEBUG
155             printf("Found track: %d\n", track_number);
156             fflush(stdout);
157 #endif
158             track++;
159
160             if (track[0] && track[0] != ' ' && track[0] != '\t')
161                track++;
162
163             if (!string_is_empty(track))
164             {
165                media_skip_spaces(&track, strlen(track));
166 #ifdef MEDIA_CUE_PARSE_DEBUG
167                printf("Found track type: %s\n", track);
168                fflush(stdout);
169 #endif
170                if (!strncasecmp(track, "MODE", 4))
171                {
172                   first_data_track = track_number;
173                   found_track = true;
174                   strlcpy(track_mode, track, sizeof(track_mode));
175                }
176                else
177                   found_file = false;
178             }
179          }
180       }
181       else if (found_file && found_track && first_data_track && !strncasecmp(command, "INDEX", 5))
182       {
183          const char *index = command + 5;
184          media_skip_spaces(&index, len - 5);
185
186          if (!string_is_empty(index))
187          {
188             char *ptr             = NULL;
189             unsigned index_number = (unsigned)strtol(index, &ptr, 10);
190
191             if (index_number == 1)
192             {
193                const char *pregap = index + 1;
194
195                if (pregap[0] && pregap[0] != ' ' && pregap[0] != '\t')
196                   pregap++;
197
198                if (!string_is_empty(pregap))
199                {
200                   media_skip_spaces(&pregap, strlen(pregap));
201                   found_file = false;
202                   found_track = false;
203
204                   if (first_data_track && !string_is_empty(track_mode))
205                   {
206                      unsigned track_sector_size = 0;
207                      unsigned track_mode_number = 0;
208
209                      if (strlen(track_mode) == 10)
210                      {
211                         sscanf(track_mode, "MODE%d/%d", (int*)&track_mode_number, (int*)&track_sector_size);
212 #ifdef MEDIA_CUE_PARSE_DEBUG
213                         printf("Found track mode %d with sector size %d\n", track_mode_number, track_sector_size);
214                         fflush(stdout);
215 #endif
216                         if ((track_mode_number == 1 || track_mode_number == 2) && track_sector_size)
217                         {
218                            unsigned min = 0;
219                            unsigned sec = 0;
220                            unsigned frame = 0;
221                            sscanf(pregap, "%02d:%02d:%02d", (int*)&min, (int*)&sec, (int*)&frame);
222
223                            if (min || sec || frame || strstr(pregap, "00:00:00"))
224                            {
225                               data_track_pregap_bytes = ((min * 60 + sec) * 75 + frame) * track_sector_size;
226 #ifdef MEDIA_CUE_PARSE_DEBUG
227                               printf("Found pregap of %02d:%02d:%02d (bytes: %" PRIu64 ")\n", min, sec, frame, data_track_pregap_bytes);
228                               fflush(stdout);
229 #endif
230                               break;
231                            }
232                         }
233                      }
234                   }
235                }
236             }
237          }
238       }
239
240       free(line);
241    }
242
243    filestream_close(file);
244
245    if (!string_is_empty(track_path))
246    {
247       if (strstr(track_path, "/") || strstr(track_path, "\\"))
248       {
249 #ifdef MEDIA_CUE_PARSE_DEBUG
250          printf("using path %s\n", track_path);
251          fflush(stdout);
252 #endif
253          return media_detect_cd_info(track_path, data_track_pregap_bytes, info);
254       }
255
256       fill_pathname_basedir(track_abs_path, path, sizeof(track_abs_path));
257       strlcat(track_abs_path, track_path, sizeof(track_abs_path));
258 #ifdef MEDIA_CUE_PARSE_DEBUG
259       printf("using abs path %s\n", track_abs_path);
260       fflush(stdout);
261 #endif
262       return media_detect_cd_info(track_abs_path, data_track_pregap_bytes, info);
263    }
264
265    return true;
266 }
267
268 /* Fill in "info" with detected CD info. Use this when you want to open a specific track file directly, and the pregap is known. */
269 bool media_detect_cd_info(const char *path, uint64_t pregap_bytes, media_detect_cd_info_t *info)
270 {
271    RFILE *file;
272
273    if (string_is_empty(path) || !info)
274       return false;
275
276    file = filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0);
277
278    if (!file)
279    {
280 #ifdef MEDIA_CUE_PARSE_DEBUG
281       printf("[MEDIA] Could not open path for reading: %s\n", path);
282       fflush(stdout);
283 #endif
284       return false;
285    }
286
287    {
288       unsigned offset = 0;
289       unsigned sector_size = 0;
290       unsigned buf_size = 17 * 2352;
291       char *buf = (char*)calloc(1, buf_size);
292       int64_t read_bytes = 0;
293
294       if (!buf)
295          return false;
296
297       if (pregap_bytes)
298          filestream_seek(file, pregap_bytes, RETRO_VFS_SEEK_POSITION_START);
299
300       read_bytes = filestream_read(file, buf, buf_size);
301
302       if (read_bytes != buf_size)
303       {
304 #ifdef MEDIA_CUE_PARSE_DEBUG
305          printf("[MEDIA] Could not read from media: got %" PRId64 " bytes instead of %d.\n", read_bytes, buf_size);
306          fflush(stdout);
307 #endif
308          filestream_close(file);
309          free(buf);
310          return false;
311       }
312
313       /* 12-byte sync field at the start of every sector, common to both mode1 and mode2 data tracks
314        * (when at least sync data is requested). This is a CD-ROM standard feature and not specific to any game devices,
315        * and as such should not be part of any system-specific detection or "magic" bytes.
316        * Depending on what parts of a sector were requested from the disc, the user data might start at
317        * byte offset 0, 4, 8, 12, 16 or 24. Cue sheets only specify the total number of bytes requested from the sectors
318        * of a track (like 2048 or 2352) and it is then assumed based on the size/mode as to what fields are present. */
319       if (!memcmp(buf, "\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12))
320       {
321          /* Assume track data contains all fields. */
322          sector_size = 2352;
323
324          if (buf[15] == 2)
325          {
326             /* assume Mode 2 formed (formless is rarely used) */
327             offset = 24;
328          }
329          else
330          {
331             /* assume Mode 1 */
332             offset = 16;
333          }
334       }
335       else
336       {
337          /* Assume sectors only contain user data instead. */
338          offset = 0;
339          sector_size = 2048;
340       }
341
342       if (!memcmp(buf + offset, "SEGADISCSYSTEM",
343                STRLEN_CONST("SEGADISCSYSTEM")))
344       {
345          const char *title_pos  = NULL;
346          const char *serial_pos = NULL;
347
348          /* All discs currently in Redump for MCD start with SEGADISCSYSTEM. There are other strings mentioned elsewhere online,
349           * but I have not seen any real examples of them. */
350          info->system_id = MEDIA_CD_SYSTEM_MEGA_CD;
351
352          strcpy_literal(info->system, "Sega CD / Mega CD");
353
354          title_pos = buf + offset + 0x150;
355
356          if (media_skip_spaces(&title_pos, 48))
357          {
358             memcpy(info->title, title_pos, 48 - (title_pos - (buf + offset + 0x150)));
359             media_zero_trailing_spaces(info->title, sizeof(info->title));
360          }
361          else
362          {
363             info->title[0] = 'N';
364             info->title[1] = '/';
365             info->title[2] = 'A';
366             info->title[3] = '\0';
367          }
368
369          serial_pos = buf + offset + 0x183;
370
371          if (media_skip_spaces(&serial_pos, 8))
372          {
373             memcpy(info->serial, serial_pos, 8 - (serial_pos - (buf + offset + 0x183)));
374             media_zero_trailing_spaces(info->serial, sizeof(info->serial));
375          }
376          else
377          {
378             info->serial[0] = 'N';
379             info->serial[1] = '/';
380             info->serial[2] = 'A';
381             info->serial[3] = '\0';
382          }
383       }
384       else if (!memcmp(buf + offset, "SEGA SEGASATURN",
385                STRLEN_CONST("SEGA SEGASATURN")))
386       {
387          const char *title_pos        = NULL;
388          const char *serial_pos       = NULL;
389          const char *version_pos      = NULL;
390          const char *release_date_pos = NULL;
391
392          info->system_id = MEDIA_CD_SYSTEM_SATURN;
393
394          strcpy_literal(info->system, "Sega Saturn");
395
396          title_pos = buf + offset + 0x60;
397
398          if (media_skip_spaces(&title_pos, 112))
399          {
400             memcpy(info->title, title_pos, 112 - (title_pos - (buf + offset + 0x60)));
401             media_zero_trailing_spaces(info->title, sizeof(info->title));
402          }
403          else
404          {
405             info->title [0] = 'N';
406             info->title [1] = '/';
407             info->title [2] = 'A';
408             info->title [3] = '\0';
409          }
410
411          serial_pos = buf + offset + 0x20;
412
413          if (media_skip_spaces(&serial_pos, 10))
414          {
415             memcpy(info->serial, serial_pos, 10 - (serial_pos - (buf + offset + 0x20)));
416             media_zero_trailing_spaces(info->serial, sizeof(info->serial));
417          }
418          else
419          {
420             info->serial[0] = 'N';
421             info->serial[1] = '/';
422             info->serial[2] = 'A';
423             info->serial[3] = '\0';
424          }
425
426          version_pos = buf + offset + 0x2a;
427
428          if (media_skip_spaces(&version_pos, 6))
429          {
430             memcpy(info->version, version_pos, 6 - (version_pos - (buf + offset + 0x2a)));
431             media_zero_trailing_spaces(info->version, sizeof(info->version));
432          }
433          else
434          {
435             info->version[0] = 'N';
436             info->version[1] = '/';
437             info->version[2] = 'A';
438             info->version[3] = '\0';
439          }
440
441          release_date_pos = buf + offset + 0x30;
442
443          if (media_skip_spaces(&release_date_pos, 8))
444          {
445             memcpy(info->release_date, release_date_pos, 8 - (release_date_pos - (buf + offset + 0x30)));
446             media_zero_trailing_spaces(info->release_date, sizeof(info->release_date));
447          }
448          else
449          {
450             info->release_date[0] = 'N';
451             info->release_date[1] = '/';
452             info->release_date[2] = 'A';
453             info->release_date[3] = '\0';
454          }
455       }
456       else if (!memcmp(buf + offset, "SEGA SEGAKATANA", STRLEN_CONST("SEGA SEGAKATANA")))
457       {
458          const char *title_pos        = NULL;
459          const char *serial_pos       = NULL;
460          const char *version_pos      = NULL;
461          const char *release_date_pos = NULL;
462
463          info->system_id = MEDIA_CD_SYSTEM_DREAMCAST;
464
465          strcpy_literal(info->system, "Sega Dreamcast");
466
467          title_pos = buf + offset + 0x80;
468
469          if (media_skip_spaces(&title_pos, 96))
470          {
471             memcpy(info->title, title_pos, 96 - (title_pos - (buf + offset + 0x80)));
472             media_zero_trailing_spaces(info->title, sizeof(info->title));
473          }
474          else
475          {
476             info->title       [0] = 'N';
477             info->title       [1] = '/';
478             info->title       [2] = 'A';
479             info->title       [3] = '\0';
480          }
481
482          serial_pos = buf + offset + 0x40;
483
484          if (media_skip_spaces(&serial_pos, 10))
485          {
486             memcpy(info->serial, serial_pos, 10 - (serial_pos - (buf + offset + 0x40)));
487             media_zero_trailing_spaces(info->serial, sizeof(info->serial));
488          }
489          else
490          {
491             info->serial      [0] = 'N';
492             info->serial      [1] = '/';
493             info->serial      [2] = 'A';
494             info->serial      [3] = '\0';
495          }
496
497          version_pos = buf + offset + 0x4a;
498
499          if (media_skip_spaces(&version_pos, 6))
500          {
501             memcpy(info->version, version_pos, 6 - (version_pos - (buf + offset + 0x4a)));
502             media_zero_trailing_spaces(info->version, sizeof(info->version));
503          }
504          else
505          {
506             info->version     [0] = 'N';
507             info->version     [1] = '/';
508             info->version     [2] = 'A';
509             info->version     [3] = '\0';
510          }
511
512          release_date_pos = buf + offset + 0x50;
513
514          if (media_skip_spaces(&release_date_pos, 8))
515          {
516             memcpy(info->release_date, release_date_pos, 8 - (release_date_pos - (buf + offset + 0x50)));
517             media_zero_trailing_spaces(info->release_date, sizeof(info->release_date));
518          }
519          else
520          {
521             info->release_date[0] = 'N';
522             info->release_date[1] = '/';
523             info->release_date[2] = 'A';
524             info->release_date[3] = '\0';
525          }
526       }
527       /* Primary Volume Descriptor fields of ISO9660 */
528       else if (!memcmp(buf + offset + (16 * sector_size), "\1CD001\1\0PLAYSTATION", 19))
529       {
530          const char *title_pos = NULL;
531
532          info->system_id = MEDIA_CD_SYSTEM_PSX;
533
534          strcpy_literal(info->system, "Sony PlayStation");
535
536          title_pos = buf + offset + (16 * sector_size) + 40;
537
538          if (media_skip_spaces(&title_pos, 32))
539          {
540             memcpy(info->title, title_pos, 32 - (title_pos - (buf + offset + (16 * sector_size) + 40)));
541             media_zero_trailing_spaces(info->title, sizeof(info->title));
542          }
543          else
544          {
545             info->title       [0] = 'N';
546             info->title       [1] = '/';
547             info->title       [2] = 'A';
548             info->title       [3] = '\0';
549          }
550       }
551       else if (!memcmp(buf + offset, "\x01\x5a\x5a\x5a\x5a\x5a\x01\x00\x00\x00\x00\x00", 12))
552       {
553          info->system_id = MEDIA_CD_SYSTEM_3DO;
554
555          strcpy_literal(info->system, "3DO");
556       }
557       else if (!memcmp(buf + offset + 0x950, "PC Engine CD-ROM SYSTEM", 23))
558       {
559          info->system_id = MEDIA_CD_SYSTEM_PC_ENGINE_CD;
560
561          strcpy_literal(info->system, "TurboGrafx-CD / PC-Engine CD");
562       }
563
564       free(buf);
565    }
566
567    filestream_close(file);
568
569    return true;
570 }