/* * 86Box A hypervisor and IBM PC system emulator that specializes in * running old operating systems and software designed for IBM * PC systems and compatibles from 1981 through fairly recent * system designs based on the PCI bus. * * This file is part of the 86Box distribution. * * CD-ROM image file handling module. * * Authors: Miran Grca, * RichardG, * Cacodemon345 * * Copyright 2016-2025 Miran Grca. * Copyright 2016-2025 Miran Grca. * Copyright 2024-2025 Cacodemon345. */ #define __STDC_FORMAT_MACROS #include #include #include #include #include #include #include #include #ifdef _WIN32 # include # include #else # include #endif #define HAVE_STDARG_H #include <86box/86box.h> #include <86box/path.h> #include <86box/plat.h> #include <86box/cdrom_image_backend.h> #include #define CDROM_BCD(x) (((x) % 10) | (((x) / 10) << 4)) #define MAX_LINE_LENGTH 512 #define MAX_FILENAME_LENGTH 256 #define CROSS_LEN 512 static char temp_keyword[1024]; #ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG int cdrom_image_backend_do_log = ENABLE_CDROM_IMAGE_BACKEND_LOG; void cdrom_image_backend_log(const char *fmt, ...) { va_list ap; if (cdrom_image_backend_do_log) { va_start(ap, fmt); pclog_ex(fmt, ap); va_end(ap); } } #else # define cdrom_image_backend_log(fmt, ...) #endif typedef struct audio_file_t { SNDFILE *file; SF_INFO info; } audio_file_t; /* Audio file functions */ static int audio_read(void *priv, uint8_t *buffer, uint64_t seek, size_t count) { track_file_t *tf = (track_file_t *) priv; audio_file_t *audio = (audio_file_t *) tf->priv; uint64_t samples_seek = seek / 4; uint64_t samples_count = count / 4; if ((seek & 3) || (count & 3)) { cdrom_image_backend_log("CD Audio file: Reading on non-4-aligned boundaries.\n"); } sf_count_t res = sf_seek(audio->file, samples_seek, SEEK_SET); if (res == -1) return 0; return !!sf_readf_short(audio->file, (short *) buffer, samples_count); } static uint64_t audio_get_length(void *priv) { track_file_t *tf = (track_file_t *) priv; audio_file_t *audio = (audio_file_t *) tf->priv; /* Assume 16-bit audio, 2 channel. */ return audio->info.frames * 4ull; } static void audio_close(void *priv) { track_file_t *tf = (track_file_t *) priv; audio_file_t *audio = (audio_file_t *) tf->priv; memset(tf->fn, 0x00, sizeof(tf->fn)); if (audio && audio->file) sf_close(audio->file); free(audio); free(tf); } static track_file_t * audio_init(const char *filename, int *error) { track_file_t *tf = (track_file_t *) calloc(sizeof(track_file_t), 1); audio_file_t *audio = (audio_file_t *) calloc(sizeof(audio_file_t), 1); #ifdef _WIN32 wchar_t filename_w[4096]; #endif if (tf == NULL || audio == NULL) { goto cleanup_error; } memset(tf->fn, 0x00, sizeof(tf->fn)); strncpy(tf->fn, filename, sizeof(tf->fn) - 1); #ifdef _WIN32 mbstowcs(filename_w, filename, 4096); audio->file = sf_wchar_open(filename_w, SFM_READ, &audio->info); #else audio->file = sf_open(filename, SFM_READ, &audio->info); #endif if (!audio->file) { cdrom_image_backend_log("Audio file open error!"); goto cleanup_error; } if (audio->info.channels != 2 || audio->info.samplerate != 44100 || !audio->info.seekable) { cdrom_image_backend_log("Audio file not seekable or in non-CD format!"); sf_close(audio->file); goto cleanup_error; } *error = 0; tf->priv = audio; tf->fp = NULL; tf->close = audio_close; tf->get_length = audio_get_length; tf->read = audio_read; return tf; cleanup_error: free(tf); free(audio); *error = 1; return NULL; } /* Binary file functions. */ static int bin_read(void *priv, uint8_t *buffer, uint64_t seek, size_t count) { track_file_t *tf = NULL; if ((tf = (track_file_t *) priv)->fp == NULL) return 0; cdrom_image_backend_log("CDROM: binary_read(%08lx, pos=%" PRIu64 " count=%lu)\n", tf->fp, seek, count); if (fseeko64(tf->fp, seek, SEEK_SET) == -1) { cdrom_image_backend_log("CDROM: binary_read failed during seek!\n"); return -1; } if (fread(buffer, count, 1, tf->fp) != 1) { cdrom_image_backend_log("CDROM: binary_read failed during read!\n"); return -1; } if (UNLIKELY(tf->motorola)) { for (uint64_t i = 0; i < count; i += 2) { uint8_t buffer0 = buffer[i]; uint8_t buffer1 = buffer[i + 1]; buffer[i] = buffer1; buffer[i + 1] = buffer0; } } return 1; } static uint64_t bin_get_length(void *priv) { track_file_t *tf = NULL; if ((tf = (track_file_t *) priv)->fp == NULL) return 0; fseeko64(tf->fp, 0, SEEK_END); const off64_t len = ftello64(tf->fp); cdrom_image_backend_log("CDROM: binary_length(%08lx) = %" PRIu64 "\n", tf->fp, len); return len; } static void bin_close(void *priv) { track_file_t *tf = (track_file_t *) priv; if (tf == NULL) return; if (tf->fp != NULL) { fclose(tf->fp); tf->fp = NULL; } memset(tf->fn, 0x00, sizeof(tf->fn)); free(priv); } static track_file_t * bin_init(const char *filename, int *error) { track_file_t *tf = (track_file_t *) calloc(1, sizeof(track_file_t)); struct stat stats; if (tf == NULL) { *error = 1; return NULL; } memset(tf->fn, 0x00, sizeof(tf->fn)); strncpy(tf->fn, filename, sizeof(tf->fn) - 1); tf->fp = plat_fopen64(tf->fn, "rb"); cdrom_image_backend_log("CDROM: binary_open(%s) = %08lx\n", tf->fn, tf->fp); if (stat(tf->fn, &stats) != 0) { /* Use a blank structure if stat failed. */ memset(&stats, 0, sizeof(struct stat)); } *error = ((tf->fp == NULL) || ((stats.st_mode & S_IFMT) == S_IFDIR)); /* Set the function pointers. */ if (!*error) { tf->read = bin_read; tf->get_length = bin_get_length; tf->close = bin_close; } else { /* From the check above, error may still be non-zero if opening a directory. * The error is set for viso to try and open the directory following this function. * However, we need to make sure the descriptor is closed. */ if ((tf->fp != NULL) && ((stats.st_mode & S_IFMT) == S_IFDIR)) { /* tf is freed by bin_close */ bin_close(tf); } else free(tf); tf = NULL; } return tf; } static track_file_t * track_file_init(const char *filename, int *error, int *is_viso) { track_file_t *tf; *is_viso = 0; /* Current we only support .BIN files, either combined or one per track. In the future, more is planned. */ tf = bin_init(filename, error); if (*error) { if ((tf != NULL) && (tf->close != NULL)) { tf->close(tf); tf = NULL; } tf = viso_init(filename, error); if (!*error) *is_viso = 1; } return tf; } static void index_file_close(track_index_t *idx) { if (idx == NULL) return; if (idx->file == NULL) return; if (idx->file->close == NULL) return; idx->file->close(idx->file); idx->file = NULL; } void cdi_get_raw_track_info(cd_img_t *cdi, int *num, uint8_t *buffer) { int len = 0; cdrom_image_backend_log("cdi->tracks_num = %i\n", cdi->tracks_num); for (int i = 0; i < cdi->tracks_num; i++) { track_t *ct = &(cdi->tracks[i]); #ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG int old_len = len; #endif buffer[len++] = ct->session; /* Session number */ buffer[len++] = ct->attr; /* Track ADR and Control */ buffer[len++] = ct->tno; /* TNO (always 0) */ buffer[len++] = ct->point; /* Point (for track points - track number) */ for (int j = 0; j < 4; j++) buffer[len++] = ct->extra[j]; buffer[len++] = (ct->idx[1].start / 75) / 60; buffer[len++] = (ct->idx[1].start / 75) % 60; buffer[len++] = ct->idx[1].start % 75; cdrom_image_backend_log("%i: %02X %02X %02X %02X %02X %02X %02X\n", i, buffer[old_len], buffer[old_len + 1], buffer[old_len + 2], buffer[old_len + 3], buffer[old_len + 8], buffer[old_len + 9], buffer[old_len + 10]); } *num = cdi->tracks_num; } static int cdi_get_track(cd_img_t *cdi, uint32_t sector) { int ret = -1; for (int i = 0; i < cdi->tracks_num; i++) { track_t *ct = &(cdi->tracks[i]); for (int j = 0; j < 3; j++) { track_index_t *ci = &(ct->idx[j]); if (((sector + 150) >= ci->start) && ((sector + 150) <= (ci->start + ci->length - 1))) { ret = i; break; } } } return ret; } static void cdi_get_track_and_index(cd_img_t *cdi, uint32_t sector, int *track, int *index) { *track = -1; *index = -1; for (int i = 0; i < cdi->tracks_num; i++) { track_t *ct = &(cdi->tracks[i]); for (int j = 0; j < 3; j++) { track_index_t *ci = &(ct->idx[j]); if (((sector + 150) >= ci->start) && ((sector + 150) <= (ci->start + ci->length - 1))) { *track = i; *index = j; break; } } } } /* TODO: See if track start is adjusted by 150 or not. */ int cdi_get_audio_sub(cd_img_t *cdi, uint32_t sector, uint8_t *attr, uint8_t *track, uint8_t *index, TMSF *rel_pos, TMSF *abs_pos) { int cur_track = cdi_get_track(cdi, sector); if (cur_track < 1) return 0; *track = (uint8_t) cur_track; const track_t *trk = &cdi->tracks[*track]; *attr = trk->attr; *index = 1; /* Absolute position should be adjusted by 150, not the relative ones. */ FRAMES_TO_MSF(sector + 150, &abs_pos->min, &abs_pos->sec, &abs_pos->fr); /* Relative position is relative Index 1 start - pre-gap values will be negative. */ FRAMES_TO_MSF((int32_t) (sector + 150 - trk->idx[1].start), &rel_pos->min, &rel_pos->sec, &rel_pos->fr); return 1; } static __inline int bin2bcd(int x) { return (x % 10) | ((x / 10) << 4); } int cdi_read_sector(cd_img_t *cdi, uint8_t *buffer, int raw, uint32_t sector) { const uint64_t sect = (uint64_t) sector; int m = 0; int s = 0; int f = 0; int ret = 0; uint64_t offset = 0ULL; int track; int index; int raw_size; int cooked_size; uint8_t q[16] = { 0x00 }; cdi_get_track_and_index(cdi, sector, &track, &index); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); const track_index_t *idx = &(trk->idx[index]); const int track_is_raw = ((trk->sector_size == RAW_SECTOR_SIZE) || (trk->sector_size == 2448)); const uint64_t seek = (sect + 150 - idx->start + idx->file_start) * trk->sector_size; cdrom_image_backend_log("cdrom_read_sector(%08X): track %02X, index %02X, %016" PRIX64 ", %016" PRIX64 ", %i\n", sector, track, index, idx->start, trk->sector_size); if (track_is_raw) raw_size = trk->sector_size; else raw_size = 2448; if ((trk->mode == 2) && (trk->form != 1)) { if (trk->form == 2) cooked_size = (track_is_raw ? 2328 : trk->sector_size); /* Both 2324 + ECC and 2328 variants are valid. */ else cooked_size = 2336; } else cooked_size = COOKED_SECTOR_SIZE; if ((trk->mode == 2) && (trk->form >= 1)) offset = 24ULL; else offset = 16ULL; if (idx->type < INDEX_NORMAL) { memset(buffer, 0x00, 2448); if (trk->attr & 0x04) { /* Construct the rest of the raw sector. */ memset(buffer + 1, 0xff, 10); buffer += 12; FRAMES_TO_MSF(sector + 150, &m, &s, &f); /* These have to be BCD. */ buffer[0] = CDROM_BCD(m & 0xff); buffer[1] = CDROM_BCD(s & 0xff); buffer[2] = CDROM_BCD(f & 0xff); /* Data, should reflect the actual sector type. */ buffer[3] = trk->mode; ret = 1; } } else if (raw && !track_is_raw) { memset(buffer, 0x00, 2448); /* We are doing a raw read but the track is cooked, length should be cooked size. */ const int temp = idx->file->read(idx->file, buffer + offset, seek, cooked_size); if (temp <= 0) return temp; if (trk->attr & 0x04) { /* Construct the rest of the raw sector. */ memset(buffer + 1, 0xff, 10); buffer += 12; FRAMES_TO_MSF(sector + 150, &m, &s, &f); /* These have to be BCD. */ buffer[0] = CDROM_BCD(m & 0xff); buffer[1] = CDROM_BCD(s & 0xff); buffer[2] = CDROM_BCD(f & 0xff); /* Data, should reflect the actual sector type. */ buffer[3] = trk->mode; ret = 1; } } else if (!raw && track_is_raw) /* The track is raw but we are doing a cooked read, length should be cooked size. */ return idx->file->read(idx->file, buffer, seek + offset, cooked_size); else { /* The track is raw and we are doing a raw read, length should be raw size. */ ret = idx->file->read(idx->file, buffer, seek, raw_size); if (raw && (raw_size == 2448)) return ret; } /* Construct Q. */ q[0] = (trk->attr >> 4) | ((trk->attr & 0xf) << 4); q[1] = bin2bcd(trk->point); q[2] = index; if (index == 0) { /* Pre-gap sector relative frame addresses count from 00:01:74 downwards. */ FRAMES_TO_MSF((int32_t) (149 - (sector + 150 - idx->start)), &m, &s, &f); } else { FRAMES_TO_MSF((int32_t) (sector + 150 - idx->start), &m, &s, &f); } q[3] = bin2bcd(m); q[4] = bin2bcd(s); q[5] = bin2bcd(f); FRAMES_TO_MSF(sector + 150, &m, &s, &f); q[7] = bin2bcd(m); q[8] = bin2bcd(s); q[9] = bin2bcd(f); /* Construct raw subchannel data from Q only. */ for (int i = 0; i < 12; i++) for (int j = 0; j < 8; j++) buffer[2352 + (i << 3) + j] = ((q[i] >> (7 - j)) & 0x01) << 6; return ret; } /* TODO: Do CUE+BIN images with a sector size of 2448 even exist? */ int cdi_read_sector_sub(cd_img_t *cdi, uint8_t *buffer, uint32_t sector) { int track; int index; cdi_get_track_and_index(cdi, sector, &track, &index); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); const track_index_t *idx = &(trk->idx[index]); const uint64_t seek = (((uint64_t) sector + 150 - idx->start + idx->file_start) * trk->sector_size); if ((idx->type < INDEX_NORMAL) && (trk->sector_size != 2448)) return 0; return idx->file->read(idx->file, buffer, seek, 2448); } int cdi_get_sector_size(cd_img_t *cdi, uint32_t sector) { int track = cdi_get_track(cdi, sector); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); return trk->sector_size; } int cdi_is_audio(cd_img_t *cdi, uint32_t sector) { int track = cdi_get_track(cdi, sector); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); return !!(trk->mode == 0); } int cdi_is_pre(cd_img_t *cdi, uint32_t sector) { int track = cdi_get_track(cdi, sector); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); return !!(trk->attr & 0x01); } int cdi_is_mode2(cd_img_t *cdi, uint32_t sector) { int track = cdi_get_track(cdi, sector); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); return !!(trk->mode == 2); } int cdi_get_mode2_form(cd_img_t *cdi, uint32_t sector) { int track = cdi_get_track(cdi, sector); if (track < 0) return 0; const track_t *trk = &(cdi->tracks[track]); return trk->form; } static int cdi_can_read_pvd(track_file_t *file, uint64_t sector_size, int mode2, int form) { uint8_t pvd[COOKED_SECTOR_SIZE]; uint64_t seek = 16ULL * sector_size; /* First VD is located at sector 16. */ if (sector_size == RAW_SECTOR_SIZE) { if (mode2 && (form > 0)) seek += 24; else seek += 16; } else if (form > 0) seek += 8; file->read(file, pvd, seek, COOKED_SECTOR_SIZE); return ((pvd[0] == 1 && !strncmp((char *) (&pvd[1]), "CD001", 5) && pvd[6] == 1) || (pvd[8] == 1 && !strncmp((char *) (&pvd[9]), "CDROM", 5) && pvd[14] == 1)); } static int cdi_cue_get_buffer(char *str, char **line, int up) { char *s = *line; char *p = str; int quote = 0; int done = 0; int space = 1; /* Copy to local buffer until we have end of string or whitespace. */ while (!done) { switch (*s) { case '\0': if (quote) { /* Ouch, unterminated string.. */ return 0; } done = 1; break; case '\"': quote ^= 1; break; case ' ': case '\t': if (space) break; if (!quote) { done = 1; break; } fallthrough; default: if (up && islower((int) *s)) *p++ = toupper((int) *s); else *p++ = *s; space = 0; break; } if (!done) s++; } *p = '\0'; *line = s; return 1; } static int cdi_cue_get_keyword(char **dest, char **line) { int success; success = cdi_cue_get_buffer(temp_keyword, line, 1); if (success) *dest = temp_keyword; return success; } /* Get a string from the input line, handling quotes properly. */ static uint64_t cdi_cue_get_number(char **line) { char temp[128]; uint64_t num; if (!cdi_cue_get_buffer(temp, line, 0)) return 0; if (sscanf(temp, "%" PRIu64, &num) != 1) return 0; return num; } static int cdi_cue_get_frame(uint64_t *frames, char **line) { char temp[128]; int min = 0; int sec = 0; int fr = 0; int success; success = cdi_cue_get_buffer(temp, line, 0); if (!success) return 0; success = sscanf(temp, "%d:%d:%d", &min, &sec, &fr) == 3; if (!success) return 0; *frames = MSF_TO_FRAMES(min, sec, fr); return 1; } static int cdi_cue_get_flags(track_t *cur, char **line) { char temp[128]; char temp2[128]; int success; success = cdi_cue_get_buffer(temp, line, 0); if (!success) return 0; memset(temp2, 0x00, sizeof(temp2)); success = sscanf(temp, "%s", temp2) == 1; if (!success) return 0; if (strstr(temp2, "PRE") != NULL) cur->attr |= 0x01; if (strstr(temp2, "DCP") != NULL) cur->attr |= 0x02; if (strstr(temp2, "4CH") != NULL) cur->attr |= 0x08; return 1; } static track_t * cdi_insert_track(cd_img_t *cdi, uint8_t session, uint8_t point) { track_t *ct = NULL; cdi->tracks_num++; if (cdi->tracks == NULL) { cdi->tracks = calloc(1, sizeof(track_t)); ct = &(cdi->tracks[0]); } else { cdi->tracks = realloc(cdi->tracks, cdi->tracks_num * sizeof(track_t)); ct = &(cdi->tracks[cdi->tracks_num - 1]); } cdrom_image_backend_log("%02X: cdi->tracks[%2i] = %016" PRIX64 "\n", point, cdi->tracks_num - 1, (uint64_t) ct); memset(ct, 0x00, sizeof(track_t)); ct->session = session; ct->point = point; for (int i = 0; i < 3; i++) ct->idx[i].type = (point > 99) ? INDEX_SPECIAL : INDEX_NONE; return ct; } static void cdi_last_3_passes(cd_img_t *cdi) { track_t *ct = NULL; track_t *lt = NULL; track_index_t *ci = NULL; track_file_t *tf = NULL; uint64_t tf_len = 0ULL; uint64_t cur_pos = 0ULL; int map[256] = { 0 }; int lead[3] = { 0 }; int pos = 0; int ls = 0; uint64_t spg[256] = { 0ULL }; track_t *lo[256] = { 0 }; cdrom_image_backend_log("A2 = %016" PRIX64 "\n", (uint64_t) &(cdi->tracks[2])); for (int i = 0; i < cdi->tracks_num; i++) { ct = &(cdi->tracks[i]); if (((ct->point >= 1) && (ct->point <= 99)) || (ct->point >= 0xb0)) { if (ct->point == 0xb0) { /* Point B0h found, add the previous three lead tracks. */ for (int j = 0; j < 3; j++) { map[pos] = lead[j]; pos++; } } map[pos] = i; pos++; } else if ((ct->point >= 0xa0) && (ct->point <= 0xa2)) lead[ct->point & 0x03] = i; } /* The last lead tracks. */ for (int i = 0; i < 3; i++) { map[pos] = lead[i]; pos++; } #ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG cdrom_image_backend_log("pos = %i, cdi->tracks_num = %i\n", pos, cdi->tracks_num); for (int i = 0; i < pos; i++) cdrom_image_backend_log("map[%02i] = %02X\n", i, map[i]); #endif cdrom_image_backend_log("Second pass:\n"); for (int i = (cdi->tracks_num - 1); i >= 0; i--) { ct = &(cdi->tracks[map[i]]); if (ct->idx[1].type != INDEX_SPECIAL) { for (int j = 2; j >= 0; j--) { ci = &(ct->idx[j]); if ((ci->type >= INDEX_ZERO) && (ci->file != tf)) { tf = ci->file; if (tf != NULL) { tf_len = tf->get_length(tf) / ct->sector_size; cdrom_image_backend_log(" File length: %016" PRIX64 " sectors\n", tf_len); } } if (ci->type == INDEX_NONE) { /* Index was not in the cue sheet, keep its length at zero. */ ci->file_start = tf_len; } else if (ci->type == INDEX_NORMAL) { /* Index was in the cue sheet and is present in the file. */ ci->file_length = tf_len - ci->file_start; tf_len -= ci->file_length; } cdrom_image_backend_log(" TRACK %2i (%2i), ATTR %02X, INDEX %i: %2i, file_start = %016" PRIX64 " (%2i:%02i:%02i), file_length = %016" PRIX64 " (%2i:%02i:%02i)\n", i, map[i], ct->attr, j, ci->type, ci->file_start, (int) ((ci->file_start / 75) / 60), (int) ((ci->file_start / 75) % 60), (int) (ci->file_start % 75), ci->file_length, (int) ((ci->file_length / 75) / 60), (int) ((ci->file_length / 75) % 60), (int) (ci->file_length % 75)); } } } cdrom_image_backend_log("Third pass:\n"); for (int i = 0; i < cdi->tracks_num; i++) { int session_changed = 0; ct = &(cdi->tracks[map[i]]); if (ct->idx[1].type != INDEX_SPECIAL) { if (ct->session != ls) { /* The first track of a session always has a pre-gap of at least 0:02:00. */ ci = &(ct->idx[0]); if (ci->type == INDEX_NONE) { ci->type = INDEX_ZERO; ci->start = 0ULL; ci->length = 150ULL; } session_changed = 1; ls = ct->session; } for (int j = 0; j < 3; j++) { ci = &(ct->idx[j]); if (ci->type == INDEX_NONE) /* Index was not in the cue sheet, keep its length at zero. */ ci->start = cur_pos; else if (ci->type == INDEX_ZERO) { /* Index was in the cue sheet and is not present in the file. */ ci->start = cur_pos; cur_pos += ci->length; } else if (ci->type == INDEX_NORMAL) { /* Index was in the cue sheet and is present in the file. */ ci->start = cur_pos; ci->length = ci->file_length; cur_pos += ci->file_length; } cdrom_image_backend_log(" TRACK %2i (%2i) (%2i), ATTR %02X, MODE %i, INDEX %i: %2i, " "start = %016" PRIX64 " (%2i:%02i:%02i), length = %016" PRIX64 " (%2i:%02i:%02i)\n", i, map[i], ct->point, ct->attr, ct->mode, j, ci->type, ci->start, (int) ((ci->start / 75) / 60), (int) ((ci->start / 75) % 60), (int) (ci->start % 75), ci->length, (int) ((ci->length / 75) / 60), (int) ((ci->length / 75) % 60), (int) (ci->length % 75)); /* Set the pre-gap of the first track of this session. */ if (session_changed) spg[ct->session] = ct->idx[0].start; } } } /* Set the lead out starts for all sessions. */ for (int i = 0; i <= ls; i++) { lo[i] = NULL; for (int j = (cdi->tracks_num - 1); j >= 0; j--) { track_t *jt = &(cdi->tracks[j]); if ((jt->session == ct->session) && (jt->point >= 1) && (jt->point <= 99)) { lo[i] = &(cdi->tracks[j]); break; } } } cdrom_image_backend_log("Fourth pass:\n"); for (int i = 0; i < cdi->tracks_num; i++) { ct = &(cdi->tracks[i]); lt = NULL; switch (ct->point) { case 0xa0: for (int j = 0; j < cdi->tracks_num; j++) { track_t *jt = &(cdi->tracks[j]); if ((jt->session == ct->session) && (jt->point >= 1) && (jt->point <= 99)) { lt = &(cdi->tracks[j]); break; } } if (lt != NULL) { int disc_type = 0x00; ct->attr = lt->attr; ct->mode = lt->mode; ct->form = lt->form; if (lt->mode == 2) disc_type = (lt->form > 0) ? 0x20 : 0x10; for (int j = 0; j < 3; j++) { ci = &(ct->idx[j]); ci->type = INDEX_ZERO; ci->start = (lt->point * 60 * 75) + (disc_type * 75); ci->length = 0; } } break; case 0xa1: for (int j = (cdi->tracks_num - 1); j >= 0; j--) { track_t *jt = &(cdi->tracks[j]); if ((jt->session == ct->session) && (jt->point >= 1) && (jt->point <= 99)) { lt = &(cdi->tracks[j]); break; } } if (lt != NULL) { ct->attr = lt->attr; ct->mode = lt->mode; ct->form = lt->form; for (int j = 0; j < 3; j++) { ci = &(ct->idx[j]); ci->type = INDEX_ZERO; ci->start = (lt->point * 60 * 75); ci->length = 0; } } break; case 0xa2: if (lo[ct->session] != NULL) { lt = lo[ct->session]; ct->attr = lt->attr; ct->mode = lt->mode; ct->form = lt->form; if (ct->idx[1].type != INDEX_NORMAL) { track_index_t *li = &(lt->idx[2]); for (int j = 0; j < 3; j++) { ci = &(ct->idx[j]); ci->type = INDEX_ZERO; ci->start = li->start + li->length; ci->length = 0; } } } break; case 0xb0: /* B0 MSF (*NOT* PMSF) points to the beginning of the pre-gap of the corresponding session's first track. */ ct->extra[0] = (spg[ct->session] / 75) / 60; ct->extra[1] = (spg[ct->session] / 75) % 60; ct->extra[2] = spg[ct->session] % 75; /* B0 PMSF points to the start of the lead out track of the last session. */ if (lo[ls] != NULL) { lt = lo[ls]; track_index_t *li = &(lt->idx[2]); ct->idx[1].start = li->start + li->length; } break; } #ifdef ENABLE_CDROM_IMAGE_BACKEND_LOG if ((ct->point >= 0xa0) && (ct->point <= 0xa2)) cdrom_image_backend_log(" TRACK %02X, SESSION %i: start = %016" PRIX64 " (%2i:%02i:%02i)\n", ct->point, ct->session, ct->idx[1].start, (int) ((ct->idx[1].start / 75) / 60), (int) ((ct->idx[1].start / 75) % 60), (int) (ct->idx[1].start % 75)); #endif } } int cdi_load_iso(cd_img_t *cdi, const char *filename) { track_t *ct = NULL; track_index_t *ci = NULL; track_file_t *tf = NULL; int success; int error = 1; int is_viso = 0; cdi->tracks = NULL; success = 1; cdrom_image_backend_log("First pass:\n"); cdi->tracks_num = 0; cdi_insert_track(cdi, 1, 0xa0); cdi_insert_track(cdi, 1, 0xa1); cdi_insert_track(cdi, 1, 0xa2); /* Data track (shouldn't there be a lead in track?). */ tf = track_file_init(filename, &error, &is_viso); if (error) { cdrom_image_backend_log("ISO: cannot open file '%s'!\n", filename); if (tf != NULL) { tf->close(tf); tf = NULL; } success = 0; } else if (is_viso) success = 3; if (success) { ct = cdi_insert_track(cdi, 1, 1); ci = &(ct->idx[1]); ct->form = 0; ct->mode = 0; for (int i = 0; i < 3; i++) ct->idx[i].type = INDEX_NONE; ct->attr = DATA_TRACK; /* Try to detect ISO type. */ ct->mode = 1; ct->form = 0; ci->type = INDEX_NORMAL; ci->file_start = 0ULL; ci->file = tf; /* For Mode 2 XA, skip the first 8 bytes in every sector when sector size = 2336. */ if (cdi_can_read_pvd(ci->file, RAW_SECTOR_SIZE, 0, 0)) ct->sector_size = RAW_SECTOR_SIZE; else if (cdi_can_read_pvd(ci->file, 2336, 1, 0)) { ct->sector_size = 2336; ct->mode = 2; } else if (cdi_can_read_pvd(ci->file, 2324, 1, 2)) { ct->sector_size = 2324; ct->mode = 2; ct->form = 2; } else if (cdi_can_read_pvd(ci->file, 2328, 1, 2)) { ct->sector_size = 2328; ct->mode = 2; ct->form = 2; } else if (cdi_can_read_pvd(ci->file, 2336, 1, 1)) { ct->sector_size = 2336; ct->mode = 2; ct->form = 1; ct->skip = 8; } else if (cdi_can_read_pvd(ci->file, RAW_SECTOR_SIZE, 1, 0)) { ct->sector_size = RAW_SECTOR_SIZE; ct->mode = 2; } else if (cdi_can_read_pvd(ci->file, RAW_SECTOR_SIZE, 1, 1)) { ct->sector_size = RAW_SECTOR_SIZE; ct->mode = 2; ct->form = 1; } else { /* We use 2048 mode 1 as the default. */ ct->sector_size = COOKED_SECTOR_SIZE; } cdrom_image_backend_log("TRACK 1: Mode = %i, Form = %i, Sector size = %08X\n", ct->mode, ct->form, ct->sector_size); } tf = NULL; if (!success) return 0; cdi_last_3_passes(cdi); return success; } int cdi_load_cue(cd_img_t *cdi, const char *cuefile) { track_t *ct = NULL; track_index_t *ci = NULL; track_file_t *tf = NULL; uint64_t frame = 0ULL; uint64_t last = 0ULL; uint8_t session = 1; int success; int error; int is_viso = 0; int lead[3] = { 0 }; char pathname[MAX_FILENAME_LENGTH]; char buf[MAX_LINE_LENGTH]; FILE *fp; char *line; char *command; char *type; char temp; cdi->tracks = NULL; cdi->tracks_num = 0; /* Get a copy of the filename into pathname, we need it later. */ memset(pathname, 0, MAX_FILENAME_LENGTH * sizeof(char)); path_get_dirname(pathname, cuefile); /* Open the file. */ fp = plat_fopen(cuefile, "r"); if (fp == NULL) return 0; success = 0; cdrom_image_backend_log("First pass:\n"); cdi->tracks_num = 0; for (int i = 0; i < 3; i++) { lead[i] = cdi->tracks_num; (void *) cdi_insert_track(cdi, session, 0xa0 + i); } cdrom_image_backend_log("lead[2] = %016" PRIX64 "\n", (uint64_t) &(cdi->tracks[lead[2]])); while (1) { line = buf; /* Read a line from the cuesheet file. */ if (feof(fp) || (fgets(buf, sizeof(buf), fp) == NULL) || ferror(fp)) break; /* Do two iterations to make sure to nuke even if it's \r\n or \n\r, but do checks to make sure we're not nuking other bytes. */ for (uint8_t i = 0; i < 2; i++) { if (strlen(buf) > 0) { if (buf[strlen(buf) - 1] == '\n') buf[strlen(buf) - 1] = '\0'; /* nuke trailing newline */ else if (buf[strlen(buf) - 1] == '\r') buf[strlen(buf) - 1] = '\0'; /* nuke trailing newline */ } } cdrom_image_backend_log(" line = %s\n", line); (void) cdi_cue_get_keyword(&command, &line); if (!strcmp(command, "FILE")) { /* The file for the track. */ char filename[MAX_FILENAME_LENGTH]; char ansi[MAX_FILENAME_LENGTH]; tf = NULL; memset(ansi, 0, MAX_FILENAME_LENGTH * sizeof(char)); memset(filename, 0, MAX_FILENAME_LENGTH * sizeof(char)); success = cdi_cue_get_buffer(ansi, &line, 0); if (!success) break; success = cdi_cue_get_keyword(&type, &line); if (!success) break; error = 1; is_viso = 0; if (!strcmp(type, "BINARY") || !strcmp(type, "MOTOROLA")) { if (!path_abs(ansi)) path_append_filename(filename, pathname, ansi); else strcpy(filename, ansi); tf = track_file_init(filename, &error, &is_viso); if (tf) tf->motorola = !strcmp(type, "MOTOROLA"); } else if (!strcmp(type, "WAVE") || !strcmp(type, "AIFF") || !strcmp(type, "MP3")) { if (!path_abs(ansi)) path_append_filename(filename, pathname, ansi); else strcpy(filename, ansi); tf = audio_init(filename, &error); } if (error) { cdrom_image_backend_log("CUE: cannot open file '%s' in cue sheet!\n", filename); if (tf != NULL) { tf->close(tf); tf = NULL; } success = 0; } else if (is_viso) success = 3; } else if (!strcmp(command, "TRACK")) { int t = cdi_cue_get_number(&line); success = cdi_cue_get_keyword(&type, &line); if (!success) break; ct = cdi_insert_track(cdi, session, t); cdrom_image_backend_log(" TRACK %i\n", t); ct->form = 0; ct->mode = 0; if (!strcmp(type, "AUDIO")) { ct->sector_size = RAW_SECTOR_SIZE; ct->attr = AUDIO_TRACK; } else if (!memcmp(type, "MODE", 4)) { uint32_t mode; ct->attr = DATA_TRACK; sscanf(type, "MODE%" PRIu32 "/%" PRIu32, &mode, &(ct->sector_size)); ct->mode = mode; if (ct->mode == 2) switch(ct->sector_size) { case 2324: case 2328: ct->form = 2; break; case 2048: case 2336: case 2352: case 2448: ct->form = 1; break; } if ((ct->sector_size == 2336) && (ct->mode == 2) && (ct->form == 1)) ct->skip = 8; } else if (!memcmp(type, "CD", 2)) { ct->attr = DATA_TRACK; ct->mode = 2; sscanf(type, "CD%c/%i", &temp, &(ct->sector_size)); } else success = 0; if (success) last = ct->sector_size; } else if (!strcmp(command, "INDEX")) { int t = cdi_cue_get_number(&line); ci = &(ct->idx[t]); cdrom_image_backend_log(" INDEX %i (1)\n", t); ci->type = INDEX_NORMAL; ci->file = tf; success = cdi_cue_get_frame(&frame, &line); ci->file_start = frame; } else if (!strcmp(command, "PREGAP")) { ci = &(ct->idx[0]); cdrom_image_backend_log(" INDEX 0 (0)\n"); ci->type = INDEX_ZERO; ci->file = tf; success = cdi_cue_get_frame(&frame, &line); ci->length = frame; } else if (!strcmp(command, "PAUSE")) { ci = &(ct->idx[1]); cdrom_image_backend_log(" INDEX 1 (0)\n"); ci->type = INDEX_ZERO; ci->file = tf; success = cdi_cue_get_frame(&frame, &line); ci->length = frame; } else if (!strcmp(command, "POSTGAP")) { ci = &(ct->idx[2]); cdrom_image_backend_log(" INDEX 2 (0)\n"); ci->type = INDEX_ZERO; ci->file = tf; success = cdi_cue_get_frame(&frame, &line); ci->length = frame; } else if (!strcmp(command, "ZERO")) { ci = &(ct->idx[1]); cdrom_image_backend_log(" INDEX 1 (0)\n"); ci->type = INDEX_ZERO; ci->file = tf; success = cdi_cue_get_frame(&frame, &line); ci->length = frame; } else if (!strcmp(command, "FLAGS")) success = cdi_cue_get_flags(ct, &line); else if (!strcmp(command, "REM")) { success = 1; char *space = strstr(line, " "); if (space != NULL) { space++; if (space < (line + strlen(line))) { (void) cdi_cue_get_keyword(&command, &space); if (!strcmp(command, "LEAD-OUT")) { ct = &(cdi->tracks[lead[2]]); cdrom_image_backend_log("lead[2] = %016" PRIX64 "\n", (uint64_t) ct); ct->sector_size = last; ci = &(ct->idx[1]); ci->type = INDEX_NORMAL; ci->file = tf; success = cdi_cue_get_frame(&frame, &space); ci->file_start = frame; cdrom_image_backend_log(" LEAD-OUT\n"); } else if (!strcmp(command, "SESSION")) { session = cdi_cue_get_number(&space); if (session > 1) { ct = cdi_insert_track(cdi, session - 1, 0xb0); ci = &(ct->idx[1]); ci->start = (0x40 * 60 * 75) + (0x02 * 75); if (session == 2) { ct->extra[3] = 0x02; /* 5F:00:00 on Wembley, C0:00:00 in the spec. And what's in PMSF? */ ct = cdi_insert_track(cdi, session - 1, 0xc0); ci = &(ct->idx[1]); ct->extra[0] = 0x5f; /* Optimum recording power. */ } else ct->extra[3] = 0x01; for (int i = 0; i < 3; i++) { lead[i] = cdi->tracks_num; (void *) cdi_insert_track(cdi, session, 0xa0 + i); } cdrom_image_backend_log("lead[2] = %016" PRIX64 "\n", (uint64_t) &(cdi->tracks[lead[2]])); } cdrom_image_backend_log(" SESSION %i\n", session); } } } } else if (!strcmp(command, "CATALOG") || !strcmp(command, "CDTEXTFILE") || !strcmp(command, "ISRC") || !strcmp(command, "PERFORMER") || !strcmp(command, "SONGWRITER") || !strcmp(command, "TITLE") || !strcmp(command, "")) /* Ignored commands. */ success = 1; else { cdrom_image_backend_log("CUE: unsupported command '%s' in cue sheet!\n", command); success = 0; } if (!success) break; } tf = NULL; fclose(fp); if (!success) return 0; cdi_last_3_passes(cdi); return success; } /* Root functions. */ static void cdi_clear_tracks(cd_img_t *cdi) { track_file_t *last = NULL; track_t *cur = NULL; track_index_t *idx = NULL; if ((cdi->tracks == NULL) || (cdi->tracks_num == 0)) return; for (int i = 0; i < cdi->tracks_num; i++) { cur = &cdi->tracks[i]; if ((cur->point >= 1) && (cur->point <= 99)) for (int j = 0; j < 3; j++) { idx = &(cur->idx[j]); /* Make sure we do not attempt to close a NULL file. */ if (idx->file != NULL) { if (idx->file != last) { last = idx->file; index_file_close(idx); } else idx->file = NULL; } } } /* Now free the array. */ free(cdi->tracks); cdi->tracks = NULL; /* Mark that there's no tracks. */ cdi->tracks_num = 0; } void cdi_close(cd_img_t *cdi) { cdi_clear_tracks(cdi); free(cdi); } int cdi_set_device(cd_img_t *cdi, const char *path) { uintptr_t ext = path + strlen(path) - strrchr(path, '.'); int ret; cdrom_image_backend_log("cdi_set_device(): %" PRIu64 ", %lli, %s\n", ext, strlen(path), path + strlen(path) - ext + 1); if ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "CUE")) { if ((ret = cdi_load_cue(cdi, path))) return ret; cdi_clear_tracks(cdi); } if ((ret = cdi_load_iso(cdi, path))) return ret; cdi_close(cdi); return 0; }