comparison cdimage.c @ 2114:2449c88cea36

Enhance support for CUE files and add initial support for cdrdao TOC files
author Michael Pavone <pavone@retrodev.com>
date Wed, 16 Feb 2022 21:22:12 -0800
parents cue.c@0db3af42dd72
children cd057d6fe030
comparison
equal deleted inserted replaced
2113:0013362c320c 2114:2449c88cea36
1 #include <ctype.h>
2 #include <string.h>
3
4 #include "system.h"
5 #include "util.h"
6
7 static char* cmd_start(char *cur)
8 {
9 while (*cur && isblank(*cur))
10 {
11 cur++;
12 }
13 return cur;
14 }
15
16 static char* cmd_start_sameline(char *cur)
17 {
18 while (*cur && isblank(*cur) && *cur != '\n')
19 {
20 cur++;
21 }
22 return cur;
23 }
24
25 static char* word_end(char *cur)
26 {
27 while (*cur && !isblank(*cur))
28 {
29 cur++;
30 }
31 return cur;
32 }
33
34 static char* next_line(char *cur)
35 {
36 while (*cur && *cur != '\n')
37 {
38 cur++;
39 }
40 if (*cur) {
41 return cur + 1;
42 }
43 return NULL;
44 }
45
46 static char* next_blank(char *cur)
47 {
48 while (*cur && !isblank(*cur))
49 {
50 cur++;
51 }
52 return cur;
53 }
54
55 static uint32_t timecode_to_lba(char *timecode)
56 {
57 char *end;
58 int seconds = 0, frames = 0;
59 int minutes = strtol(timecode, &end, 10);
60 if (end) {
61 timecode = end + 1;
62 seconds = strtol(timecode, &end, 10);
63 if (end) {
64 timecode = end + 1;
65 frames = strtol(timecode, NULL, 10);
66 }
67 }
68 seconds += minutes * 60;
69 return seconds * 75 + frames;
70
71 }
72
73 enum {
74 FAKE_DATA = 1,
75 FAKE_AUDIO,
76 };
77
78 static uint8_t bin_seek(system_media *media, uint32_t sector)
79 {
80 media->cur_sector = sector;
81 uint32_t lba = sector;
82 uint32_t track;
83 for (track = 0; track < media->num_tracks; track++)
84 {
85 if (lba < media->tracks[track].fake_pregap) {
86 media->in_fake_pregap = media->tracks[track].type == TRACK_DATA ? FAKE_DATA : FAKE_AUDIO;
87 break;
88 }
89 lba -= media->tracks[track].fake_pregap;
90 if (lba < media->tracks[track].start_lba) {
91 if (media->tracks[track].fake_pregap) {
92 media->in_fake_pregap = media->tracks[track].type == TRACK_DATA ? FAKE_DATA : FAKE_AUDIO;
93 } else {
94 media->in_fake_pregap = 0;
95 }
96 break;
97 }
98 if (lba < media->tracks[track].end_lba) {
99 media->in_fake_pregap = 0;
100 break;
101 }
102 }
103 if (track < media->num_tracks) {
104 fprintf(stderr, "bin_seek to sector %u, adjusted lba %u, track %u\n", sector, lba, track);
105 media->cur_track = track;
106 if (!media->in_fake_pregap) {
107 if (track) {
108 lba -= media->tracks[track - 1].end_lba;
109 }
110 fseek(media->tracks[track].f, media->tracks[track].file_offset + lba * media->tracks[track].sector_bytes, SEEK_SET);
111 }
112 }
113 return track;
114 }
115
116 static uint8_t fake_read(uint32_t sector, uint32_t offset)
117 {
118 if (!offset || (offset >= 16)) {
119 return 0;
120 //TODO: error detection and correction bytes
121 } else if (offset < 12) {
122 return 0xFF;
123 } else if (offset == 12) {
124 uint32_t minute = (sector / 75) / 60;
125 return (minute % 10) | ((minute / 10 ) << 4);
126 } else if (offset == 13) {
127 uint32_t seconds = (sector / 75) % 60;
128 return (seconds % 10) | ((seconds / 10 ) << 4);
129 } else if (offset == 14) {
130 uint32_t frames = sector % 75;
131 return (frames % 10) | ((frames / 10 ) << 4);
132 } else {
133 return 1;
134 }
135 }
136
137 static uint8_t bin_read(system_media *media, uint32_t offset)
138 {
139 if (media->in_fake_pregap == FAKE_DATA) {
140 return fake_read(media->cur_sector, offset);
141 } else if (media->in_fake_pregap == FAKE_AUDIO) {
142 return 0;
143 } else if ((media->tracks[media->cur_track].sector_bytes < 2352 && offset < 16) || offset > (media->tracks[media->cur_track].sector_bytes + 16)) {
144 return fake_read(media->cur_sector, offset);
145 } else {
146 if (media->tracks[media->cur_track].need_swap) {
147 if (offset & 1) {
148 return media->byte_storage;
149 }
150 media->byte_storage = fgetc(media->tracks[media->cur_track].f);
151 }
152 return fgetc(media->tracks[media->cur_track].f);
153 }
154 }
155
156 uint8_t parse_cue(system_media *media)
157 {
158 char *line = media->buffer;
159 media->num_tracks = 0;
160 do {
161 char *cmd = cmd_start(line);
162 if (cmd) {
163 if (startswith(cmd, "TRACK ")) {
164 media->num_tracks++;
165 }
166 line = next_line(cmd);
167 } else {
168 line = NULL;
169 }
170 } while (line);
171 track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
172 media->tracks = tracks;
173 line = media->buffer;
174 int track = -1;
175 uint8_t audio_byte_swap = 0;
176 FILE *f = NULL;
177 int track_of_file = -1;
178 uint8_t has_index_0 = 0;
179 do {
180 char *cmd = cmd_start(line);
181 if (*cmd) {
182 if (startswith(cmd, "TRACK ")) {
183 track++;
184 track_of_file++;
185 has_index_0 = 0;
186 cmd += 6;
187 char *end;
188 int file_track = strtol(cmd, &end, 10);
189 if (file_track != (track + 1)) {
190 warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
191 }
192 tracks[track].f = f;
193
194
195 cmd = cmd_start(end);
196 if (*cmd) {
197 if (startswith(cmd, "AUDIO")) {
198 tracks[track].type = TRACK_AUDIO;
199 tracks[track].need_swap = audio_byte_swap;
200 tracks[track].sector_bytes = 2352;
201 } else {
202 tracks[track].type = TRACK_DATA;
203 tracks[track].need_swap = 0;
204 tracks[track].sector_bytes = 0;
205 char *slash = strchr(cmd, '/');
206 if (slash) {
207 tracks[track].sector_bytes = atoi(slash+1);
208 }
209 if (!tracks[track].sector_bytes) {
210 warning("Missing sector size for data track %d in cue", track + 1);
211 tracks[track].sector_bytes = 2352;
212 }
213 }
214
215 }
216 } else if (startswith(cmd, "FILE ")) {
217 cmd += 5;
218 cmd = strchr(cmd, '"');
219 if (cmd) {
220 cmd++;
221 char *end = strchr(cmd, '"');
222 if (end) {
223 char *fname;
224 //TODO: zipped BIN/CUE support
225 if (is_absolute_path(cmd)) {
226 fname = malloc(end-cmd + 1);
227 memcpy(fname, cmd, end-cmd);
228 fname[end-cmd] = 0;
229 } else {
230 size_t dirlen = strlen(media->dir);
231 fname = malloc(dirlen + 1 + (end-cmd) + 1);
232 memcpy(fname, media->dir, dirlen);
233 fname[dirlen] = PATH_SEP[0];
234 memcpy(fname + dirlen + 1, cmd, end-cmd);
235 fname[dirlen + 1 + (end-cmd)] = 0;
236 }
237 f = fopen(fname, "rb");
238 if (!f) {
239 fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
240 }
241 free(fname);
242 track_of_file = -1;
243 for (end++; *end && *end != '\n' && *end != '\r'; end++)
244 {
245 if (!isspace(*end)) {
246 if (startswith(end, "BINARY")) {
247 audio_byte_swap = 0;
248 } else if (startswith(end, "MOTOROLA")) {
249 audio_byte_swap = 1;
250 } else {
251 warning("Unsupported FILE type in CUE sheet. Only BINARY and MOTOROLA are supported\n");
252 }
253 break;
254 }
255 }
256 }
257 }
258 } else if (track >= 0) {
259 if (startswith(cmd, "PREGAP ")) {
260 tracks[track].fake_pregap = timecode_to_lba(cmd + 7);
261 } else if (startswith(cmd, "INDEX ")) {
262 char *after;
263 int index = strtol(cmd + 6, &after, 10);
264 uint8_t has_start_lba = 0;
265 uint32_t start_lba;
266 if (!index) {
267 tracks[track].pregap_lba = start_lba = timecode_to_lba(after);
268 has_index_0 = 1;
269 has_start_lba = 1;
270 } else if (index == 1) {
271 tracks[track].start_lba = timecode_to_lba(after);
272 if (!has_index_0) {
273 start_lba = tracks[track].start_lba;
274 has_start_lba = 1;
275 }
276 }
277 if (has_start_lba) {
278 if (track > 0) {
279 tracks[track-1].end_lba = start_lba;
280 }
281 if (track_of_file > 0) {
282 tracks[track].file_offset = tracks[track-1].file_offset + tracks[track-1].end_lba * tracks[track-1].sector_bytes;
283 if (track_of_file > 1) {
284 tracks[track].file_offset -= tracks[track-2].end_lba * tracks[track-1].sector_bytes;
285 }
286 } else {
287 tracks[track].file_offset = 0;
288 }
289 }
290 }
291 }
292 if (cmd && *cmd) {
293 line = next_line(cmd);
294 } else {
295 line = NULL;
296 }
297 } else {
298 line = NULL;
299 }
300 } while (line);
301 if (media->num_tracks > 0 && media->tracks[0].f) {
302 //end of last track is implicitly defined by file size
303 if (tracks[media->num_tracks-1].f) {
304 uint32_t start_lba =tracks[media->num_tracks-1].pregap_lba ? tracks[media->num_tracks-1].pregap_lba : tracks[media->num_tracks-1].start_lba;
305 tracks[media->num_tracks-1].end_lba = start_lba + (file_size(tracks[media->num_tracks-1].f) - tracks[media->num_tracks-1].file_offset)/ tracks[media->num_tracks-1].sector_bytes;
306 }
307 //replace cue sheet with first sector
308 free(media->buffer);
309 media->buffer = calloc(2048, 1);
310 if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352) {
311 // if the first track is a data track, don't trust the CUE sheet and look at the MM:SS:FF from first sector
312 uint8_t msf[3];
313 fseek(tracks[0].f, 12, SEEK_SET);
314 if (sizeof(msf) == fread(msf, 1, sizeof(msf), tracks[0].f)) {
315 tracks[0].fake_pregap = msf[2] + (msf[0] * 60 + msf[1]) * 75;
316 }
317 } else if (!tracks[0].start_lba && !tracks[0].fake_pregap) {
318 tracks[0].fake_pregap = 2 * 75;
319 }
320
321 fseek(tracks[0].f, tracks[0].sector_bytes == 2352 ? 16 : 0, SEEK_SET);
322 media->size = fread(media->buffer, 1, 2048, tracks[0].f);
323 media->seek = bin_seek;
324 media->read = bin_read;
325 }
326 uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
327 media->type = valid ? MEDIA_CDROM : MEDIA_CART;
328 return valid;
329 }
330
331 uint8_t parse_toc(system_media *media)
332 {
333 char *line = media->buffer;
334 media->num_tracks = 0;
335 do {
336 char *cmd = cmd_start(line);
337 if (cmd) {
338 if (startswith(cmd, "TRACK ")) {
339 media->num_tracks++;
340 }
341 line = next_line(cmd);
342 } else {
343 line = NULL;
344 }
345 } while (line);
346 track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
347 media->tracks = tracks;
348 line = media->buffer;
349 char *last_file_name = NULL;
350 FILE *f = NULL;
351 int track = -1;
352 do {
353 char *cmd = cmd_start(line);
354 if (*cmd) {
355 if (startswith(cmd, "TRACK ")) {
356 track++;
357 cmd = cmd_start(cmd + 6);
358 if (startswith(cmd, "AUDIO")) {
359 tracks[track].type = TRACK_AUDIO;
360 tracks[track].sector_bytes = 2352;
361 tracks[track].need_swap = 1;
362 } else {
363 tracks[track].type = TRACK_DATA;
364 tracks[track].need_swap = 0;
365 if (startswith(cmd, "MODE1_RAW") || startswith(cmd, "MODE2_RAW")) {
366 tracks[track].sector_bytes = 2352;
367 } else if (startswith(cmd, "MODE2_FORM2")) {
368 tracks[track].sector_bytes = 2324;
369 } else if (startswith(cmd, "MODE1") || startswith(cmd, "MODE2_FORM1")) {
370 tracks[track].sector_bytes = 2048;
371 } else if (startswith(cmd, "MODE2")) {
372 tracks[track].sector_bytes = 2336;
373 }
374 }
375 cmd = word_end(cmd);
376 if (*cmd && *cmd != '\n') {
377 cmd = cmd_start_sameline(cmd);
378 if (*cmd && *cmd != '\n') {
379 //TODO: record whether subcode is in raw format or not
380 if (startswith(cmd, "RW_RAW")) {
381 tracks[track].sector_bytes += 96;
382 } else if (startswith(cmd, "RW")) {
383 tracks[track].sector_bytes += 96;
384 }
385 }
386 }
387 if (track) {
388 tracks[track].start_lba = tracks[track].pregap_lba = tracks[track].end_lba = tracks[track-1].end_lba;
389 }
390 } else if (track >= 0) {
391 uint8_t is_datafile = startswith(cmd, "DATAFILE");
392 if (is_datafile || startswith(cmd, "FILE")) {
393
394 if (tracks[track].f) {
395 warning("TOC file has more than one file for track %d, only one is supported\n", track + 1);
396 } else {
397 cmd += 8;
398 char *fname_start = strchr(cmd, '"');
399 if (fname_start) {
400 ++fname_start;
401 char *fname_end = strchr(fname_start, '"');
402 if (fname_end) {
403 if (!last_file_name || strncmp(last_file_name, fname_start, fname_end-fname_start)) {
404 free(last_file_name);
405 last_file_name = calloc(1, 1 + fname_end-fname_start);
406 memcpy(last_file_name, fname_start, fname_end-fname_start);
407 char *fname;
408 //TODO: zipped BIN/TOC support
409 if (is_absolute_path(last_file_name)) {
410 fname = last_file_name;
411 } else {
412 size_t dirlen = strlen(media->dir);
413 fname = malloc(dirlen + 1 + (fname_end-fname_start) + 1);
414 memcpy(fname, media->dir, dirlen);
415 fname[dirlen] = PATH_SEP[0];
416 memcpy(fname + dirlen + 1, fname_start, fname_end-fname_start);
417 fname[dirlen + 1 + (fname_end-fname_start)] = 0;
418 }
419 f = fopen(fname, "rb");
420 if (!f) {
421 fatal_error("Failed to open %s specified by DATAFILE command in TOC file %s.%s\n", fname, media->name, media->extension);
422 }
423 if (fname != last_file_name) {
424 free(fname);
425 }
426 }
427 tracks[track].f = f;
428 cmd = fname_end + 1;
429 cmd = cmd_start_sameline(cmd);
430 if (*cmd == '#') {
431 char *end;
432 tracks[track].file_offset = strtol(cmd + 1, &end, 10);
433 cmd = cmd_start_sameline(end);
434 }
435 if (!is_datafile) {
436 if (isdigit(*cmd)) {
437 uint32_t start = timecode_to_lba(cmd);
438 tracks[track].file_offset += start * tracks[track].sector_bytes;
439 cmd = next_blank(cmd);
440 }
441 }
442 if (isdigit(*cmd)) {
443 uint32_t length = timecode_to_lba(cmd);
444 tracks[track].end_lba += length;
445 } else {
446 long fsize = file_size(f);
447 tracks[track].end_lba += fsize - tracks[track].file_offset;
448 }
449 }
450 }
451 }
452 } else if (startswith(cmd, "SILENCE")) {
453 cmd = cmd_start_sameline(cmd + 7);
454 tracks[track].fake_pregap += timecode_to_lba(cmd);
455 } else if (startswith(cmd, "START")) {
456 cmd = cmd_start_sameline(cmd + 5);
457 tracks[track].start_lba = tracks[track].pregap_lba + timecode_to_lba(cmd);
458 }
459 }
460 if (cmd && *cmd) {
461 line = next_line(cmd);
462 } else {
463 line = NULL;
464 }
465 } else {
466 line = NULL;
467 }
468 } while (line);
469 if (media->num_tracks > 0 && media->tracks[0].f) {
470 //replace cue sheet with first sector
471 free(media->buffer);
472 media->buffer = calloc(2048, 1);
473 if (tracks[0].type == TRACK_DATA && tracks[0].sector_bytes == 2352) {
474 // if the first track is a data track, don't trust the TOC file and look at the MM:SS:FF from first sector
475 uint8_t msf[3];
476 fseek(tracks[0].f, 12, SEEK_SET);
477 if (sizeof(msf) == fread(msf, 1, sizeof(msf), tracks[0].f)) {
478 tracks[0].fake_pregap = msf[2] + (msf[0] * 60 + msf[1]) * 75;
479 }
480 } else if (!tracks[0].start_lba && !tracks[0].fake_pregap) {
481 tracks[0].fake_pregap = 2 * 75;
482 }
483
484 fseek(tracks[0].f, tracks[0].sector_bytes == 2352 ? 16 : 0, SEEK_SET);
485 media->size = fread(media->buffer, 1, 2048, tracks[0].f);
486 media->seek = bin_seek;
487 media->read = bin_read;
488 }
489 uint8_t valid = media->num_tracks > 0 && media->tracks[0].f != NULL;
490 media->type = valid ? MEDIA_CDROM : MEDIA_CART;
491 return valid;
492 }
493
494 uint32_t make_iso_media(system_media *media, const char *filename)
495 {
496 FILE *f = fopen(filename, "rb");
497 if (!f) {
498 return 0;
499 }
500 media->buffer = calloc(2048, 1);
501 media->size = fread(media->buffer, 1, 2048, f);
502 media->num_tracks = 1;
503 media->tracks = calloc(sizeof(track_info), 1);
504 media->tracks[0] = (track_info){
505 .f = f,
506 .file_offset = 0,
507 .fake_pregap = 2 * 75,
508 .start_lba = 0,
509 .end_lba = file_size(f),
510 .sector_bytes = 2048,
511 .need_swap = 0,
512 .type = TRACK_DATA
513 };
514 media->type = MEDIA_CDROM;
515 media->seek = bin_seek;
516 media->read = bin_read;
517 return media->size;
518 }