try a new github issue template
[pcsx_rearmed.git] / deps / libretro-common / formats / cdfs / cdfs.c
CommitLineData
3719602c
PC
1#include <formats/cdfs.h>
2
3#include <retro_miscellaneous.h>
4#include <compat/strl.h>
5#include <file/file_path.h>
6#include <string/stdstring.h>
7
8#ifdef HAVE_CHD
9#include <streams/chd_stream.h>
10#endif
11
12static void cdfs_determine_sector_size(cdfs_track_t* track)
13{
14 uint8_t buffer[32];
15 const int toc_sector = 16;
16
17 /* MODE information is normally found in the CUE sheet, but we can try to determine it from the raw data.
18 *
19 * MODE1/2048 - CDROM Mode1 Data (cooked) [no header, no footer]
20 * MODE1/2352 - CDROM Mode1 Data (raw) [16 byte header, 288 byte footer]
21 * MODE2/2336 - CDROM-XA Mode2 Data [8 byte header, 280 byte footer]
22 * MODE2/2352 - CDROM-XA Mode2 Data [24 byte header, 280 byte footer]
23 *
24 * Note that MODE is actually a property on each sector and can change between 1 and 2 depending on how much error
25 * correction the author desired. To support that, the data format must be "/2352" to include the full header and
26 * data without error correction information, at which point the CUE sheet information becomes just a hint.
27 */
28
29 /* The boot record or primary volume descriptor is always at sector 16 and will contain a "CD001" marker */
30 intfstream_seek(track->stream, toc_sector * 2352 + track->first_sector_offset, SEEK_SET);
31 if (intfstream_read(track->stream, &buffer, sizeof(buffer)) != (int64_t)sizeof(buffer))
32 return;
33
34 /* if this is a CDROM-XA data source, the "CD001" tag will be 25 bytes into the sector */
35 if ( buffer[25] == 0x43
36 && buffer[26] == 0x44
37 && buffer[27] == 0x30
38 && buffer[28] == 0x30
39 && buffer[29] == 0x31)
40 {
41 track->stream_sector_size = 2352;
42 track->stream_sector_header_size = 24;
43 }
44 /* otherwise it should be 17 bytes into the sector */
45 else if (buffer[17] == 0x43
46 && buffer[18] == 0x44
47 && buffer[19] == 0x30
48 && buffer[20] == 0x30
49 && buffer[21] == 0x31)
50 {
51 track->stream_sector_size = 2352;
52 track->stream_sector_header_size = 16;
53 }
54 else
55 {
56 /* ISO-9660 says the first twelve bytes of a sector should be the sync pattern 00 FF FF FF FF FF FF FF FF FF FF 00 */
57 if (
58 buffer[ 0] == 0
59 && buffer[ 1] == 0xFF
60 && buffer[ 2] == 0xFF
61 && buffer[ 3] == 0xFF
62 && buffer[ 4] == 0xFF
63 && buffer[ 5] == 0xFF
64 && buffer[ 6] == 0xFF
65 && buffer[ 7] == 0xFF
66 && buffer[ 8] == 0xFF
67 && buffer[ 9] == 0xFF
68 && buffer[10] == 0xFF
69 && buffer[11] == 0)
70 {
71 /* if we didn't find a CD001 tag, this format may predate ISO-9660 */
72
73 /* after the 12 byte sync pattern is three bytes identifying the sector and then one byte for the mode (total 16 bytes) */
74 track->stream_sector_size = 2352;
75 track->stream_sector_header_size = 16;
76 }
77 }
78}
79
80static void cdfs_determine_sector_size_from_file_size(cdfs_track_t* track)
81{
82 /* attempt to determine stream_sector_size from file size */
83 size_t size = intfstream_get_size(track->stream);
84
85 if ((size % 2352) == 0)
86 {
87 /* raw tracks use all 2352 bytes and have a 24 byte header */
88 track->stream_sector_size = 2352;
89 track->stream_sector_header_size = 24;
90 }
91 else if ((size % 2048) == 0)
92 {
93 /* cooked tracks eliminate all header/footer data */
94 track->stream_sector_size = 2048;
95 track->stream_sector_header_size = 0;
96 }
97 else if ((size % 2336) == 0)
98 {
99 /* MODE 2 format without 16-byte sync data */
100 track->stream_sector_size = 2336;
101 track->stream_sector_header_size = 8;
102 }
103}
104
105static void cdfs_seek_track_sector(cdfs_track_t* track, unsigned int sector)
106{
107 intfstream_seek(track->stream,
108 sector * track->stream_sector_size
109 + track->stream_sector_header_size
110 + track->first_sector_offset, SEEK_SET);
111}
112
113void cdfs_seek_sector(cdfs_file_t* file, unsigned int sector)
114{
115 /* only allowed if open_file was called with a NULL path */
116 if (file->first_sector == 0)
117 {
118 if (file->current_sector != (int)sector)
119 {
120 file->current_sector = (int)sector;
121 file->sector_buffer_valid = 0;
122 }
123
124 file->pos = sector * 2048;
125 file->current_sector_offset = 0;
126 }
127}
128
129uint32_t cdfs_get_num_sectors(cdfs_file_t* file)
130{
131 uint32_t frame_size = intfstream_get_frame_size(file->track->stream);
132 if (frame_size == 0)
133 {
134 frame_size = file->track->stream_sector_size;
135 if (frame_size == 0)
136 frame_size = 1; /* prevent divide by 0 error if sector size is unknown */
137 }
138 return (uint32_t)(intfstream_get_size(file->track->stream) / frame_size);
139}
140
141uint32_t cdfs_get_first_sector(cdfs_file_t* file)
142{
143 return file->track->first_sector_index;
144}
145
146static int cdfs_find_file(cdfs_file_t* file, const char* path)
147{
148 size_t path_length;
149 int sector;
150 uint8_t buffer[2048], *tmp;
151 const char* slash = strrchr(path, '\\');
152
153 if (slash)
154 {
155 /* navigate the path to the directory record for the file */
156 const int dir_length = (int)(slash - path);
157 memcpy(buffer, path, dir_length);
158 buffer[dir_length] = '\0';
159
160 sector = cdfs_find_file(file, (const char*)buffer);
161 if (sector < 0)
162 return sector;
163
164 path += dir_length + 1;
165 }
166 else
167 {
168 int offset;
169
170 /* find the CD information (always 16 frames in) */
171 cdfs_seek_track_sector(file->track, 16);
172 intfstream_read(file->track->stream, buffer, sizeof(buffer));
173
174 /* the directory_record starts at 156 bytes into the sector.
175 * the sector containing the root directory contents is a
176 * 3 byte value that is 2 bytes into the directory_record. */
177 offset = 156 + 2;
178 sector = buffer[offset] | (buffer[offset + 1] << 8) | (buffer[offset + 2] << 16);
179 }
180
181 /* process the contents of the directory */
182 cdfs_seek_track_sector(file->track, sector);
183 intfstream_read(file->track->stream, buffer, sizeof(buffer));
184
185 path_length = strlen(path);
186 tmp = buffer;
187
188 while (tmp < buffer + sizeof(buffer))
189 {
190 /* The first byte of the record is the length of
191 * the record - if 0, we reached the end of the data */
192 if (!*tmp)
193 break;
194
195 /* filename is 33 bytes into the record and
196 * the format is "FILENAME;version" or "DIRECTORY" */
197 if ( (tmp[33 + path_length] == ';'
198 || (tmp[33 + path_length] == '\0'))
199 && strncasecmp((const char*)(tmp + 33), path, path_length) == 0)
200 {
201 /* the file size is in bytes 10-13 of the record */
202 file->size =
203 (tmp[10])
204 | (tmp[11] << 8)
205 | (tmp[12] << 16)
206 | (tmp[13] << 24);
207
208 /* the file contents are in the sector identified
209 * in bytes 2-4 of the record */
210 sector = tmp[2] | (tmp[3] << 8) | (tmp[4] << 16);
211 return sector;
212 }
213
214 /* the first byte of the record is the length of the record */
215 tmp += tmp[0];
216 }
217
218 return -1;
219}
220
221int cdfs_open_file(cdfs_file_t* file, cdfs_track_t* track, const char* path)
222{
223 if (!file || !track)
224 return 0;
225
226 memset(file, 0, sizeof(*file));
227
228 file->track = track;
229 file->current_sector = -1;
230 file->first_sector = -1;
231
232 if (path)
233 file->first_sector = cdfs_find_file(file, path);
234 else if (file->track->stream_sector_size)
235 {
236 file->first_sector = 0;
237 file->size = (unsigned int)((intfstream_get_size(
238 file->track->stream) / file->track->stream_sector_size)
239 * 2048);
240 return 1;
241 }
242
243 return (file->first_sector >= 0);
244}
245
246int64_t cdfs_read_file(cdfs_file_t* file, void* buffer, uint64_t len)
247{
248 int bytes_read = 0;
249
250 if (!file || file->first_sector < 0 || !buffer)
251 return 0;
252
253 if (len > file->size - file->pos)
254 len = file->size - file->pos;
255
256 if (len == 0)
257 return 0;
258
259 if (file->sector_buffer_valid)
260 {
261 size_t remaining = 2048 - file->current_sector_offset;
262 if (remaining > 0)
263 {
264 if (remaining >= len)
265 {
266 memcpy(buffer,
267 &file->sector_buffer[file->current_sector_offset],
268 (size_t)len);
269 file->current_sector_offset += len;
270 return len;
271 }
272
273 memcpy(buffer,
274 &file->sector_buffer[file->current_sector_offset], remaining);
275 buffer = (char*)buffer + remaining;
276 bytes_read += remaining;
277 len -= remaining;
278
279 file->current_sector_offset += remaining;
280 }
281
282 ++file->current_sector;
283 file->current_sector_offset = 0;
284 file->sector_buffer_valid = 0;
285 }
286 else if (file->current_sector < file->first_sector)
287 {
288 file->current_sector = file->first_sector;
289 file->current_sector_offset = 0;
290 }
291
292 while (len >= 2048)
293 {
294 cdfs_seek_track_sector(file->track, file->current_sector);
295 intfstream_read(file->track->stream, buffer, 2048);
296
297 buffer = (char*)buffer + 2048;
298 bytes_read += 2048;
299
300 ++file->current_sector;
301
302 len -= 2048;
303 }
304
305 if (len > 0)
306 {
307 cdfs_seek_track_sector(file->track, file->current_sector);
308 intfstream_read(file->track->stream, file->sector_buffer, 2048);
309 memcpy(buffer, file->sector_buffer, (size_t)len);
310 file->current_sector_offset = (unsigned int)len;
311 file->sector_buffer_valid = 1;
312
313 bytes_read += len;
314 }
315
316 file->pos += bytes_read;
317 return bytes_read;
318}
319
320void cdfs_close_file(cdfs_file_t* file)
321{
322 /* Not really anything to do here, just
323 * clear out the first_sector so
324 * read() won't do anything */
325 if (file)
326 file->first_sector = -1;
327}
328
329int64_t cdfs_get_size(cdfs_file_t* file)
330{
331 if (!file || file->first_sector < 0)
332 return 0;
333 return file->size;
334}
335
336int64_t cdfs_tell(cdfs_file_t* file)
337{
338 if (!file || file->first_sector < 0)
339 return -1;
340 return file->pos;
341}
342
343int64_t cdfs_seek(cdfs_file_t* file, int64_t offset, int whence)
344{
345 int64_t new_pos;
346 int new_sector;
347
348 if (!file || file->first_sector < 0)
349 return -1;
350
351 switch (whence)
352 {
353 case SEEK_SET:
354 new_pos = offset;
355 break;
356
357 case SEEK_CUR:
358 new_pos = file->pos + offset;
359 break;
360
361 case SEEK_END:
362 new_pos = file->size - offset;
363 break;
364
365 default:
366 return -1;
367 }
368
369 if (new_pos < 0)
370 return -1;
371 else if (new_pos > file->size)
372 return -1;
373
374 file->pos = (unsigned int)new_pos;
375 file->current_sector_offset = file->pos % 2048;
376
377 new_sector = file->pos / 2048;
378 if (new_sector != file->current_sector)
379 {
380 file->current_sector = new_sector;
381 file->sector_buffer_valid = false;
382 }
383
384 return 0;
385}
386
387static void cdfs_skip_spaces(const char** ptr)
388{
389 while (**ptr && (**ptr == ' ' || **ptr == '\t'))
390 ++(*ptr);
391}
392
393static cdfs_track_t* cdfs_wrap_stream(
394 intfstream_t* stream,
395 unsigned first_sector_offset,
396 unsigned first_sector_index)
397{
398 cdfs_track_t* track = NULL;
399
400 if (!stream)
401 return NULL;
402
403 track = (cdfs_track_t*)
404 calloc(1, sizeof(*track));
405 track->stream = stream;
406 track->first_sector_offset = first_sector_offset;
407 track->first_sector_index = first_sector_index;
408
409 cdfs_determine_sector_size(track);
410
411 return track;
412}
413
414static cdfs_track_t* cdfs_open_cue_track(
415 const char* path, unsigned int track_index)
416{
417 char* cue = NULL;
418 const char* line = NULL;
419 int found_track = 0;
420 char current_track_path[PATH_MAX_LENGTH] = {0};
421 char track_path[PATH_MAX_LENGTH] = {0};
422 unsigned int sector_size = 0;
423 unsigned int previous_sector_size = 0;
424 unsigned int previous_index_sector_offset= 0;
425 unsigned int track_offset = 0;
426 intfstream_t *cue_stream = intfstream_open_file(path, RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
427 int64_t stream_size = intfstream_get_size(cue_stream);
428 char *cue_contents = (char*)malloc((size_t)(stream_size + 1));
429 cdfs_track_t* track = NULL;
430
431 if (!cue_contents)
432 {
433 intfstream_close(cue_stream);
434 return NULL;
435 }
436
437 intfstream_read(cue_stream, cue_contents, stream_size);
438 intfstream_close(cue_stream);
439
440 cue_contents[stream_size] = '\0';
441
442 cue = cue_contents;
443 while (*cue)
444 {
445 cdfs_skip_spaces((const char**)&cue);
446 line = cue;
447
448 while (*cue && *cue != '\n')
449 ++cue;
450 if (*cue)
451 *cue++ = '\0';
452
453 if (!strncasecmp(line, "FILE", 4))
454 {
455 const char *file = line + 4;
456 cdfs_skip_spaces(&file);
457
458 if (file[0])
459 {
460 const char *file_end = cue - 1;
461 while (file_end > file && *file_end != ' ' && *file_end != '\t')
462 --file_end;
463
464 if ( file[0] == '"'
465 && file_end[-1] == '"')
466 {
467 ++file;
468 --file_end;
469 }
470
471 memcpy(current_track_path, file, file_end - file);
472 current_track_path[file_end - file] = '\0';
473 }
474
475 previous_sector_size = 0;
476 previous_index_sector_offset = 0;
477 track_offset = 0;
478 }
479 else if (!strncasecmp(line, "TRACK", 5))
480 {
481 char *ptr = NULL;
482 unsigned track_number = 0;
483 const char *track = line + 5;
484 cdfs_skip_spaces(&track);
485
486 track_number = (unsigned)strtol(track, &ptr, 10);
487 while (*track && *track != ' ' && *track != '\n')
488 ++track;
489
490 previous_sector_size = sector_size;
491
492 cdfs_skip_spaces(&track);
493
494 if (!strncasecmp(track, "MODE", 4))
495 {
496 /* track_index = 0 means find the first data track */
497 if (!track_index || track_index == track_number)
498 found_track = track_number;
499
500 sector_size = atoi(track + 6);
501 }
502 else /* assume AUDIO */
503 sector_size = 2352;
504 }
505 else if (!strncasecmp(line, "INDEX", 5))
506 {
507 unsigned sector_offset;
508 unsigned min = 0, sec = 0, frame = 0;
509 unsigned index_number = 0;
510 const char *index = line + 5;
511
512 cdfs_skip_spaces(&index);
513 sscanf(index, "%u", &index_number);
514 while (*index && *index != ' ' && *index != '\n')
515 ++index;
516 cdfs_skip_spaces(&index);
517
518 sscanf(index, "%u:%u:%u", &min, &sec, &frame);
519 sector_offset = ((min * 60) + sec) * 75 + frame;
520 sector_offset -= previous_index_sector_offset;
521 track_offset += sector_offset * previous_sector_size;
522 previous_sector_size = sector_size;
523 previous_index_sector_offset += sector_offset;
524
525 if (found_track && index_number == 1)
526 {
527 if ( strstr(current_track_path, "/")
528 || strstr(current_track_path, "\\"))
529 strncpy(track_path, current_track_path, sizeof(track_path));
530 else
531 {
532 fill_pathname_basedir(track_path, path, sizeof(track_path));
533 strlcat(track_path, current_track_path, sizeof(track_path));
534 }
535
536 break;
537 }
538 }
539 }
540
541 free(cue_contents);
542
543 if (string_is_empty(track_path))
544 return NULL;
545
546 /* NOTE: previous_index_sector_offset will only be valid if all tracks are in the same BIN file.
547 * Otherwise, we need to determine how many tracks are in each previous BIN file, which is not
548 * stored in the CUE file. This will affect cdfs_get_first_sector, which luckily isn't used much. */
549 track = cdfs_wrap_stream(intfstream_open_file(
550 track_path, RETRO_VFS_FILE_ACCESS_READ,
551 RETRO_VFS_FILE_ACCESS_HINT_NONE), track_offset, previous_index_sector_offset);
552
553 if (track && track->stream_sector_size == 0)
554 {
555 track->stream_sector_size = sector_size;
556
557 if (sector_size == 2352)
558 track->stream_sector_header_size = 16;
559 else if (sector_size == 2336)
560 track->stream_sector_header_size = 8;
561 }
562
563 return track;
564}
565
566#ifdef HAVE_CHD
567static cdfs_track_t* cdfs_open_chd_track(const char* path, int32_t track_index)
568{
569 cdfs_track_t *track;
570 intfstream_t *intf_stream = intfstream_open_chd_track(path,
571 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE,
572 track_index);
573 if (!intf_stream)
574 return NULL;
575
576 track = cdfs_wrap_stream(intf_stream,
577 intfstream_get_offset_to_start(intf_stream),
578 intfstream_get_first_sector(intf_stream));
579
580 if (track && track->stream_sector_header_size == 0)
581 {
582 track->stream_sector_size = intfstream_get_frame_size(intf_stream);
583
584 if (track->stream_sector_size == 2352)
585 track->stream_sector_header_size = 16;
586 else if (track->stream_sector_size == 2336)
587 track->stream_sector_header_size = 8;
588 }
589
590 return track;
591}
592#endif
593
594struct cdfs_track_t* cdfs_open_track(const char* path,
595 unsigned int track_index)
596{
597 const char* ext = path_get_extension(path);
598
599 if (string_is_equal_noncase(ext, "cue"))
600 return cdfs_open_cue_track(path, track_index);
601
602#ifdef HAVE_CHD
603 if (string_is_equal_noncase(ext, "chd"))
604 return cdfs_open_chd_track(path, track_index);
605#endif
606
607 /* if opening track 1, try opening as a raw track */
608 if (track_index == 1)
609 return cdfs_open_raw_track(path);
610
611 /* unsupported file type */
612 return NULL;
613}
614
615struct cdfs_track_t* cdfs_open_data_track(const char* path)
616{
617 const char* ext = path_get_extension(path);
618
619 if (string_is_equal_noncase(ext, "cue"))
620 return cdfs_open_cue_track(path, 0);
621
622#ifdef HAVE_CHD
623 if (string_is_equal_noncase(ext, "chd"))
624 return cdfs_open_chd_track(path, CHDSTREAM_TRACK_PRIMARY);
625#endif
626
627 /* unsupported file type - try opening as a raw track */
628 return cdfs_open_raw_track(path);
629}
630
631cdfs_track_t* cdfs_open_raw_track(const char* path)
632{
633 const char *ext = path_get_extension(path);
634 cdfs_track_t *track = NULL;
635
636 if ( string_is_equal_noncase(ext, "bin")
637 || string_is_equal_noncase(ext, "iso"))
638 {
639 intfstream_t* file = intfstream_open_file(path,
640 RETRO_VFS_FILE_ACCESS_READ, RETRO_VFS_FILE_ACCESS_HINT_NONE);
641
642 track = cdfs_wrap_stream(file, 0, 0);
643 if (track && track->stream_sector_size == 0)
644 {
645 cdfs_determine_sector_size_from_file_size(track);
646 if (track->stream_sector_size == 0)
647 {
648 cdfs_close_track(track);
649 track = NULL;
650 }
651 }
652 }
653
654 return track;
655}
656
657void cdfs_close_track(cdfs_track_t* track)
658{
659 if (track)
660 {
661 if (track->stream)
662 {
663 intfstream_close(track->stream);
664 free(track->stream);
665 }
666
667 free(track);
668 }
669}