aboutsummaryrefslogtreecommitdiff
path: root/cuerecover.c
diff options
context:
space:
mode:
authorB. Watson <yalhcru@gmail.com>2020-05-11 21:43:44 -0400
committerB. Watson <yalhcru@gmail.com>2020-05-11 21:43:44 -0400
commit38a9d0ec489925eb25b8a0c837b5230da4eb8a68 (patch)
tree8ad6118bf451b1fc85c8d367bb7babe828c22273 /cuerecover.c
parent0b19a8ae810169cbdfd15ba79b3e7165e40cf535 (diff)
downloadmiragextract-38a9d0ec489925eb25b8a0c837b5230da4eb8a68.tar.gz
add cuerecover
Diffstat (limited to 'cuerecover.c')
-rw-r--r--cuerecover.c311
1 files changed, 311 insertions, 0 deletions
diff --git a/cuerecover.c b/cuerecover.c
new file mode 100644
index 0000000..34542d2
--- /dev/null
+++ b/cuerecover.c
@@ -0,0 +1,311 @@
+/* references:
+https://www.gnu.org/software/ccd2cue/manual/html_node/CUE-sheet-format.html
+https://wiki.osdev.org/User:Combuster/CDRom_BS
+https://en.wikipedia.org/wiki/CD-ROM */
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <string.h>
+#include <errno.h>
+
+const char *self;
+
+#define RAW_SECTOR_SIZE 2352
+#define DATA_SECTOR_SIZE 2048
+
+unsigned char mode1_sync[] = {
+ 0x00, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0xff,
+ 0xff, 0xff, 0xff, 0x00 };
+
+double silence_sec = 2.0l;
+int silence_frames; /* same as above in 1/75 sec frames */
+int silence_thresh = 0;
+int multi_bin = 0;
+int verbose = 0;
+
+void die(const char *msg) {
+ if(!msg) msg = strerror(errno);
+ fprintf(stderr, "%s: %s\n", self, msg);
+ exit(1);
+}
+
+void usage() {
+ fprintf(stderr, "cuerecover v" VERSION " by B. Watson, WTFPL\n");
+ fprintf(stderr, "Usage: %s [-s sec] [-t thresh] bin-file [bin-file ...]\n", self);
+ fprintf(stderr, "See man page for details\n");
+ exit(0);
+}
+
+int frame_is_silent(const char *data) {
+ int i, nonzero = 0;
+ for(i=0; i<RAW_SECTOR_SIZE; i++)
+ if(data[i]) nonzero++;
+
+ if(nonzero <= silence_thresh)
+ return 1;
+ return 0;
+}
+
+const char *frame2time(int frame) {
+ static char buf[10];
+ int min, sec;
+
+ min = frame / (75 * 60);
+ frame %= (75 * 60);
+ sec = frame / 75;
+ frame %= 75;
+
+ sprintf(buf, "%02d:%02d:%02d", min, sec, frame);
+
+ return buf;
+}
+
+int read_sector(FILE *f, char *buf) {
+ /*
+ if(verbose)
+ fprintf(stderr, "read_sector at pos %d\n", ftell(f));
+ */
+ return fread(buf, RAW_SECTOR_SIZE, 1, f);
+}
+
+int has_mode1_sync(char *buf) {
+ return (memcmp(buf, mode1_sync, sizeof(mode1_sync)) == 0);
+}
+
+int get_sector_mode(char *buf) {
+ if(!has_mode1_sync(buf))
+ return 0; /* audio, or something we don't grok */
+
+ return buf[15];
+}
+
+int unbcd(int bcd) {
+ int hi, lo;
+
+ lo = bcd & 0x0f;
+ hi = (bcd >> 4) & 0x0f;
+ return hi * 10 + lo;
+}
+
+int get_sector_addr(char *buf) {
+ int mm, ss, ff;
+ mm = unbcd(buf[12]);
+ ss = unbcd(buf[13]);
+ ff = unbcd(buf[14]);
+ return mm * 75 * 60 + ss * 75 + ff;
+}
+
+const char *mode_string(int mode) {
+ switch(mode) {
+ case 1: return "MODE1/2352";
+ case 2: return "MODE2/2352";
+ default: return "AUDIO";
+ }
+}
+
+struct track {
+ int mode;
+ int index0;
+ int index1;
+};
+
+void process_single(char *filename) {
+ char buf[RAW_SECTOR_SIZE];
+ FILE *f;
+ int i;
+ int track = 0;
+ int sector = 0;
+ int mode;
+ int silence_start, in_silence = 0;
+ struct track tracklist[100];
+ struct track *t = tracklist;
+
+ if(!(f = fopen(filename, "rb")))
+ die(NULL);
+
+ /* only the first track can be data */
+ if(read_sector(f, buf) && (mode = get_sector_mode(buf)) != 0) {
+ if(verbose) fprintf(stderr, "track %d is data, mode %d\n", track, mode);
+ t->index0 = sector;
+ t->index1 = sector;
+ t->mode = mode;
+
+ /* skip over the data sectors */
+ while(read_sector(f, buf) && (get_sector_mode(buf) == mode))
+ sector++;
+
+ /* now buf holds the first audio sector, which might or might not
+ begin with silence */
+ t++; track++;
+ t->mode = 0;
+ t->index0 = sector;
+ t->index1 = sector;
+ }
+
+ if(silence_frames) while(read_sector(f, buf)) {
+ if(frame_is_silent(buf)) {
+ if(in_silence) {
+ /* nothing */
+ } else {
+ t->index0 = sector;
+ t->index1 = -1;
+ silence_start = sector;
+ in_silence = 1;
+ }
+ } else {
+ if(in_silence) {
+ if((sector - silence_start) >= silence_frames) {
+ in_silence = 0;
+ t->index1 = sector;
+ t++;
+ track++;
+ t->mode = 0;
+ } else {
+ /* nothing */
+ }
+ }
+ }
+ sector++;
+ }
+
+ fclose(f);
+
+ t = tracklist;
+ printf("FILE \"%s\" BINARY\r\n", filename);
+ for(i=0; i<=track; t++, i++) {
+ if(verbose) {
+ fprintf(stderr, "track %d, mode %d, index0 %d, index1 %d\n",
+ i, t->mode, t->index0, t->index1);
+ }
+ if(t->index1 > -1) {
+ printf(" TRACK %02d %s\r\n", i + 1, mode_string(t->mode));
+ if(t->index0 < t->index1) {
+ if(silence_frames && t->index1 - t->index0 > silence_frames) {
+ t->index0 = t->index1 - silence_frames;
+ if(verbose)
+ fprintf(stderr, " (index0 adjusted to %d)\n", t->index0);
+ }
+ printf(" INDEX 00 %s\r\n", frame2time(t->index0));
+ }
+ printf(" INDEX 01 %s\r\n", frame2time(t->index1));
+ }
+ }
+}
+
+void process_multi(char **filenames) {
+ int track = 1;
+ char buf[RAW_SECTOR_SIZE];
+ FILE *f;
+
+ while(*filenames) {
+ int index0 = 0, index1 = -1;
+ int mode = -1;
+ int sector = 0;
+
+ if(verbose) fprintf(stderr, "reading %s\n", *filenames);
+ if(!(f = fopen(*filenames, "rb")))
+ die(NULL);
+
+ printf("FILE \"%s\" BINARY\r\n", *filenames);
+
+ while(read_sector(f, buf)) {
+ mode = get_sector_mode(buf);
+
+ /* if first sector is data, there's no index 01, we're done */
+ if(mode > 0) {
+ index0 = 0;
+ index1 = -1;
+ break;
+ }
+
+ /* for audio, keep reading silence until we hit sound */
+ if(mode == 0 && index1 == -1 && !frame_is_silent(buf)) {
+ index1 = (sector / 75) * 75; /* round to integer number of seconds */
+ break;
+ }
+
+ sector++;
+ }
+ fclose(f);
+
+ printf(" TRACK %02d %s\r\n", track, mode_string(mode));
+ if(index1 < 1) {
+ printf(" INDEX 01 %s\r\n", frame2time(index0));
+ } else {
+ printf(" INDEX 00 %s\r\n", frame2time(index0));
+ printf(" INDEX 01 %s\r\n", frame2time(index1));
+ }
+
+ track++;
+ filenames++;
+ }
+}
+
+char **parse_args(int argc, char **argv) {
+ if(argc == 1) usage();
+ if(argc > 1 && strcmp(argv[1], "--help") == 0) usage();
+
+ while(++argv, --argc) {
+ if(argv[0][0] == '-') {
+ char *nextarg = argv[1];
+ switch(argv[0][1]) {
+ case 'v': verbose++; break;
+ case 's':
+ if(nextarg && ((*nextarg >= '0' && *nextarg <= '9') || (*nextarg == '.'))) {
+ silence_sec = atof(nextarg);
+ argv++, argc--;
+ } else {
+ die("-s requires numeric seconds argument");
+ }
+ break;
+ case 't':
+ if(nextarg &&
+ (*nextarg >= '0' && *nextarg <= '9') &&
+ (silence_thresh = atoi(nextarg) <= 100))
+ {
+ silence_thresh = (RAW_SECTOR_SIZE / 100.0) * atoi(nextarg);
+ argv++, argc--;
+ } else {
+ die("-t requires numeric percentage argument, 0-100");
+ }
+ break;
+ default:
+ fprintf(stderr, "unrecognized option '%s', try --help\n", *argv);
+ exit(1);
+ break;
+ }
+ } else {
+ break;
+ }
+ }
+
+ silence_frames = (int)(75.0l * silence_sec);
+ multi_bin = (argc > 1);
+ return argv;
+}
+
+void set_exe_name(const char *argv0) {
+ const char *p;
+ self = argv0;
+ for(p = self; *p; p++)
+ if(p[0] == '/' && p[1]) self = p + 1;
+}
+
+int main(int argc, char **argv) {
+ set_exe_name(argv[0]);
+
+ argv = parse_args(argc, argv);
+
+ if(verbose) fprintf(stderr, "writing %s CUE file to stdout\n",
+ (multi_bin ? "multi-bin" : "single-bin"));
+
+ if(multi_bin)
+ process_multi(argv);
+ else
+ process_single(*argv);
+
+ return 0;
+}