mirror of
https://github.com/86Box/86Box.git
synced 2026-02-22 09:35:32 -07:00
1500 lines
45 KiB
C
1500 lines
45 KiB
C
/*
|
|
* 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, <mgrca8@gmail.com>
|
|
* RichardG, <richardg867@gmail.com>
|
|
* Cacodemon345
|
|
*
|
|
* Copyright 2016-2025 Miran Grca.
|
|
* Copyright 2016-2025 Miran Grca.
|
|
* Copyright 2024-2025 Cacodemon345.
|
|
*/
|
|
#define __STDC_FORMAT_MACROS
|
|
#include <ctype.h>
|
|
#include <inttypes.h>
|
|
#include <stdarg.h>
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <sys/stat.h>
|
|
#ifdef _WIN32
|
|
# include <string.h>
|
|
# include <sys/types.h>
|
|
#else
|
|
# include <libgen.h>
|
|
#endif
|
|
#define HAVE_STDARG_H
|
|
#include <86box/86box.h>
|
|
#include <86box/path.h>
|
|
#include <86box/plat.h>
|
|
#include <86box/cdrom_image_backend.h>
|
|
|
|
#include <sndfile.h>
|
|
|
|
#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;
|
|
}
|