Mercurial > repos > blastem
view cue.c @ 2101:17ecd28ddc8a
Fix handling of address error for 32-bit accesses
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Wed, 09 Feb 2022 23:39:33 -0800 |
parents | 0db3af42dd72 |
children |
line wrap: on
line source
#include <ctype.h> #include <string.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; } enum { FAKE_DATA = 1, FAKE_AUDIO, }; static uint8_t bin_seek(system_media *media, uint32_t sector) { media->cur_sector = sector; uint32_t lba = sector; uint32_t track; for (track = 0; track < media->num_tracks; track++) { if (lba < media->tracks[track].fake_pregap) { media->in_fake_pregap = media->tracks[track].type == TRACK_DATA ? FAKE_DATA : FAKE_AUDIO; break; } lba -= media->tracks[track].fake_pregap; if (lba < media->tracks[track].start_lba) { media->in_fake_pregap = media->tracks[track].type == TRACK_DATA ? FAKE_DATA : FAKE_AUDIO; break; } if (lba < media->tracks[track].end_lba) { media->in_fake_pregap = 0; break; } } if (track < media->num_tracks) { media->cur_track = track; } if (!media->in_fake_pregap) { fseek(media->f, lba * 2352, SEEK_SET); } return track; } static uint8_t fake_read(uint32_t sector, uint32_t offset) { if (!offset || (offset >= 16)) { return 0; //TODO: error detection and correction bytes } else if (offset < 12) { return 0xFF; } else if (offset == 12) { uint32_t minute = (sector / 75) / 60; return (minute % 10) | ((minute / 10 ) << 4); } else if (offset == 13) { uint32_t seconds = (sector / 75) % 60; return (seconds % 10) | ((seconds / 10 ) << 4); } else if (offset == 14) { uint32_t frames = sector % 75; return (frames % 10) | ((frames / 10 ) << 4); } else { return 1; } } static uint8_t bin_read(system_media *media, uint32_t offset) { if (media->in_fake_pregap == FAKE_DATA) { return fake_read(media->cur_sector, offset); } else if (media->in_fake_pregap == FAKE_AUDIO) { return 0; } else { if (media->tracks[media->cur_track].need_swap) { if (offset & 1) { return media->byte_storage; } media->byte_storage = fgetc(media->f); } return fgetc(media->f); } } static uint8_t iso_seek(system_media *media, uint32_t sector) { media->cur_sector = sector; if (sector < (2 * 75)) { media->in_fake_pregap = FAKE_DATA; } else { media->in_fake_pregap = 0; fseek(media->f, (sector - 2 * 75) * 2048, SEEK_SET); } return 0; } static uint8_t iso_read(system_media *media, uint32_t offset) { if (media->in_fake_pregap || offset < 16 || offset > (2048 + 16)) { return fake_read(media->cur_sector, offset); } else { return fgetc(media->f); } } 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; uint8_t audio_byte_swap = 0; 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; tracks[track].need_swap = tracks[track].type == TRACK_AUDIO && audio_byte_swap; } } 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); for (end++; *end && *end != '\n' && *end != '\r'; end++) { if (!isspace(*end)) { if (startswith(end, "BINARY")) { audio_byte_swap = 0; } else if (startswith(end, "MOTOROLA")) { audio_byte_swap = 1; } else { warning("Unsupported FILE type in CUE sheet. Only BINARY and MOTOROLA are supported\n"); } break; } } } } } } 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); if (tracks[0].type == TRACK_DATA) { // if the first track is a data track, don't trust the CUE sheet and look at the MM:SS:FF from first sector uint8_t msf[3]; fseek(media->f, 12, SEEK_SET); if (sizeof(msf) == fread(msf, 1, sizeof(msf), media->f)) { tracks[0].fake_pregap = msf[2] + (msf[0] * 60 + msf[1]) * 75; } } else if (!tracks[0].start_lba && !tracks[0].fake_pregap) { tracks[0].fake_pregap = 2 * 75; } fseek(media->f, 16, SEEK_SET); media->size = fread(media->buffer, 1, 2048, media->f); media->seek = bin_seek; media->read = bin_read; } uint8_t valid = tracks > 0 && media->f != NULL; media->type = valid ? MEDIA_CDROM : MEDIA_CART; return valid; } uint32_t make_iso_media(system_media *media, const char *filename) { media->f = fopen(filename, "rb"); if (!media->f) { return 0; } media->buffer = calloc(2048, 1); media->size = fread(media->buffer, 1, 2048, media->f); media->num_tracks = 1; media->tracks = calloc(sizeof(track_info), 1); media->tracks[0] = (track_info){ .fake_pregap = 2 * 75, .start_lba = 0, .end_lba = file_size(media->f), .type = TRACK_DATA }; media->type = MEDIA_CDROM; media->seek = iso_seek; media->read = iso_read; return media->size; }