changeset 2059:6399a776e981 segacd

Add basic support for BIN/CUE images
author Michael Pavone <pavone@retrodev.com>
date Fri, 21 Jan 2022 21:59:46 -0800
parents 70260f6051dd
children f1c2415f4d1d
files Makefile blastem.c cue.c cue.h system.h
diffstat 5 files changed, 179 insertions(+), 2 deletions(-) [+]
line wrap: on
line diff
--- a/Makefile	Fri Jan 21 20:24:48 2022 -0800
+++ b/Makefile	Fri Jan 21 21:59:46 2022 -0800
@@ -214,7 +214,7 @@
 
 MAINOBJS=blastem.o system.o genesis.o debug.o gdb_remote.o vdp.o $(RENDEROBJS) io.o romdb.o hash.o menu.o xband.o \
 	realtec.o i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
-	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o segacd.o lc8951.o
+	$(M68KOBJS) $(TRANSOBJS) $(AUDIOOBJS) saves.o zip.o bindings.o jcart.o gen_player.o segacd.o lc8951.o cue.o
 
 LIBOBJS=libblastem.o system.o genesis.o debug.o gdb_remote.o vdp.o io.o romdb.o hash.o xband.o realtec.o \
 	i2c.o nor.o sega_mapper.o multi_game.o megawifi.o $(NET) serialize.o $(TERMINAL) $(CONFIGOBJS) gst.o \
--- a/blastem.c	Fri Jan 21 20:24:48 2022 -0800
+++ b/blastem.c	Fri Jan 21 21:59:46 2022 -0800
@@ -30,6 +30,7 @@
 #include "bindings.h"
 #include "menu.h"
 #include "zip.h"
+#include "cue.h"
 #include "event_log.h"
 #ifndef DISABLE_NUKLEAR
 #include "nuklear_ui/blastem_nuklear.h"
@@ -235,8 +236,13 @@
 	dst->name = basename_no_extension(filename);
 	dst->extension = path_extension(filename);
 	dst->size = ret;
+	romclose(f);
+	if (!strcasecmp(dst->extension, "cue")) {
+		if (parse_cue(dst)) {
+			*stype = SYSTEM_SEGACD;
+		}
+	}
 
-	romclose(f);
 	return ret;
 }
 
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cue.c	Fri Jan 21 21:59:46 2022 -0800
@@ -0,0 +1,148 @@
+#include <ctype.h>
+
+#include "system.h"
+#include "util.h"
+
+static char* cmd_start(char *cur)
+{
+	while (*cur && isblank(*cur))
+	{
+		cur++;
+	}
+	return cur;
+}
+
+static char* next_line(char *cur)
+{
+	while (*cur && *cur != '\n')
+	{
+		cur++;
+	}
+	if (*cur) {
+		return cur + 1;
+	}
+	return NULL;
+}
+
+static uint32_t timecode_to_lba(char *timecode)
+{
+	char *end;
+	int seconds = 0, frames = 0;
+	int minutes = strtol(timecode, &end, 10);
+	if (end) {
+		timecode = end + 1;
+		seconds = strtol(timecode, &end, 10);
+		if (end) {
+			timecode = end + 1;
+			frames = strtol(timecode, NULL, 10);
+		}
+	}
+	seconds += minutes * 60;
+	return seconds * 75 + frames;
+
+}
+
+uint8_t parse_cue(system_media *media)
+{
+	char *line = media->buffer;
+	media->num_tracks = 0;
+	do {
+		char *cmd = cmd_start(line);
+		if (cmd) {
+			if (startswith(cmd, "TRACK ")) {
+				media->num_tracks++;
+			}
+			line = next_line(cmd);
+		} else {
+			line = NULL;
+		}
+	} while (line);
+	track_info *tracks = calloc(sizeof(track_info), media->num_tracks);
+	media->tracks = tracks;
+	line = media->buffer;
+	int track = -1;
+	do {
+		char *cmd = cmd_start(line);
+		if (cmd) {
+			if (startswith(cmd, "TRACK ")) {
+				track++;
+				cmd += 6;
+				char *end;
+				int file_track = strtol(cmd, &end, 10);
+				if (file_track != (track + 1)) {
+					warning("Expected track %d, but found track %d in CUE sheet\n", track + 1, file_track);
+				}
+				cmd = cmd_start(end);
+				if (cmd) {
+					tracks[track].type = startswith(cmd, "AUDIO") ? TRACK_AUDIO : TRACK_DATA;
+				}
+			} else if (startswith(cmd, "FILE ")) {
+				if (media->f) {
+					warning("CUE sheets with multiple FILE commands are not supported\n");
+				} else {
+					cmd += 5;
+					cmd = strchr(cmd, '"');
+					if (cmd) {
+						cmd++;
+						char *end = strchr(cmd, '"');
+						if (end) {
+							char *fname;
+							//TODO: zipped BIN/CUE support
+							if (is_absolute_path(cmd)) {
+								fname = malloc(end-cmd + 1);
+								memcpy(fname, cmd, end-cmd);
+								fname[end-cmd] = 0;
+							} else {
+								size_t dirlen = strlen(media->dir);
+								fname = malloc(dirlen + 1 + (end-cmd) + 1);
+								memcpy(fname, media->dir, dirlen);
+								fname[dirlen] = PATH_SEP[0];
+								memcpy(fname + dirlen + 1, cmd, end-cmd);
+								fname[dirlen + 1 + (end-cmd)] = 0;
+							}
+							media->f = fopen(fname, "rb");
+							if (!media->f) {
+								fatal_error("Failed to open %s specified by FILE command in CUE sheet %s.%s\n", fname, media->name, media->extension);
+							}
+							free(fname);
+						}
+					}
+				}
+			} else if (track >= 0) {
+				if (startswith(cmd, "PREGAP ")) {
+					tracks[track].fake_pregap = timecode_to_lba(cmd + 7);
+				} else if (startswith(cmd, "INDEX ")) {
+					char *after;
+					int index = strtol(cmd + 6, &after, 10);
+					if (!index) {
+						tracks[track].pregap_lba = timecode_to_lba(after);
+					} else if (index == 1) {
+						tracks[track].start_lba = timecode_to_lba(after);
+					}
+				}
+			}
+			if (cmd) {
+				line = next_line(cmd);
+			} else {
+				line = NULL;
+			}
+		} else {
+			line = NULL;
+		}
+	} while (line);
+	for (uint32_t i = 0; i < (media->num_tracks - 1); i++)
+	{
+		uint32_t next = i + 1;
+		tracks[i].end_lba = tracks[next].pregap_lba ? tracks[next].pregap_lba : tracks[next].start_lba;
+	}
+	if (media->f) {
+		//end of last track is implicitly defined by file size
+		tracks[media->num_tracks-1].end_lba = file_size(media->f) / 2352;
+		//replace cue sheet with first sector
+		free(media->buffer);
+		media->buffer = calloc(2048, 1);
+		fseek(media->f, 16, SEEK_SET);
+		media->size = fread(media->buffer, 1, 2048, media->f);
+	}
+	return tracks > 0 && media->f != NULL;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/cue.h	Fri Jan 21 21:59:46 2022 -0800
@@ -0,0 +1,6 @@
+#ifndef CUE_H_
+#define CUE_H_
+
+uint8_t parse_cue(system_media *media);
+
+#endif //CUE_H_
--- a/system.h	Fri Jan 21 20:24:48 2022 -0800
+++ b/system.h	Fri Jan 21 21:59:46 2022 -0800
@@ -2,6 +2,7 @@
 #define SYSTEM_H_
 #include <stddef.h>
 #include <stdint.h>
+#include <stdio.h>
 
 typedef struct system_header system_header;
 typedef struct system_media system_media;
@@ -84,12 +85,28 @@
 	MEDIA_CDROM
 } media_type;
 
+typedef enum {
+	TRACK_AUDIO,
+	TRACK_DATA
+} track_type;
+
+typedef struct {
+	uint32_t   fake_pregap;
+	uint32_t   pregap_lba;
+	uint32_t   start_lba;
+	uint32_t   end_lba;
+	track_type type;
+} track_info;
+
 struct system_media {
 	void         *buffer;
 	char         *dir;
 	char         *name;
 	char         *extension;
 	system_media *chain;
+	track_info   *tracks;
+	FILE         *f;
+	uint32_t     num_tracks;
 	uint32_t     size;
 	media_type   type;
 };