Files
86Box/src/cdrom/cdrom_image.c
2025-07-26 22:10:56 -04:00

2673 lines
87 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 RichardG.
* Copyright 2024-2025 Cacodemon345.
*/
#define __STDC_FORMAT_MACROS
#include <ctype.h>
#include <inttypes.h>
#ifdef ENABLE_IMAGE_LOG
#include <stdarg.h>
#endif
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <wchar.h>
#include <sys/stat.h>
#ifndef _WIN32
# include <libgen.h>
#endif
#include <86box/86box.h>
#include <86box/log.h>
#include <86box/path.h>
#include <86box/plat.h>
#include <86box/cdrom.h>
#include <86box/cdrom_image.h>
#include <86box/cdrom_image_viso.h>
#include <sndfile.h>
#define MAX_LINE_LENGTH 512
#define MAX_FILENAME_LENGTH 256
#define CROSS_LEN 512
static char temp_keyword[1024];
#define INDEX_SPECIAL -2 /* Track A0h onwards. */
#define INDEX_NONE -1 /* Empty block. */
#define INDEX_ZERO 0 /* Block not in the file, return all 0x00's. */
#define INDEX_NORMAL 1 /* Block in the file. */
typedef struct track_index_t {
/*
Is the current block in the file? If not, return all 0x00's. -1 means not
yet loaded.
*/
int32_t type;
/* The amount of bytes to skip at the beginning of each sector. */
int32_t skip;
/*
Starting and ending sector LBA - negative in order to accomodate LBA -150 to -1
to read the pregap of track 1.
*/
uint64_t start;
uint64_t length;
uint64_t file_start;
uint64_t file_length;
track_file_t *file;
} track_index_t;
typedef struct track_t {
uint8_t session;
uint8_t attr;
uint8_t tno;
uint8_t point;
uint8_t extra[4];
uint8_t mode;
uint8_t form;
uint8_t subch_type;
uint8_t skip;
uint32_t sector_size;
track_index_t idx[3];
} track_t;
/*
MDS for DVD has the disc structure table - 4 byte pointer to BCA,
followed by the copyright, DMI, and layer pages.
*/
#pragma pack(push, 1)
typedef struct
{
uint8_t f1[4];
uint8_t f4[2048];
uint8_t f0[2048];
} layer_t;
typedef struct
{
layer_t layers[2];
} mds_disc_struct_t;
#pragma pack(pop)
#define dstruct_t mds_disc_struct_t
typedef struct cd_image_t {
cdrom_t *dev;
void *log;
int is_dvd;
int has_audio;
int has_dstruct;
int32_t tracks_num;
uint32_t bad_sectors_num;
track_t *tracks;
uint32_t *bad_sectors;
dstruct_t dstruct;
} cd_image_t;
typedef enum
{
CD = 0x00, /* CD-ROM */
CD_R = 0x01, /* CD-R */
CD_RW = 0x02, /* CD-RW */
DVD = 0x10, /* DVD-ROM */
DVD_MINUS_R = 0x12 /* DVD-R */
} mds_medium_type_t;
typedef enum
{
UNKNOWN = 0x00,
AUDIO = 0xa9, /* sector size = 2352 */
MODE1 = 0xaa, /* sector size = 2048 */
MODE2 = 0xab, /* sector size = 2336 */
MODE2_FORM1 = 0xac, /* sector size = 2048 */
MODE2_FORM2 = 0xad /* sector size = 2324 (+4) */
} mds_trk_mode_t;
typedef enum
{
NONE = 0x00, /* no subchannel */
PW_INTERLEAVED = 0x08 /* 96-byte PW subchannel, interleaved */
} mds_subch_mode_t;
#pragma pack(push, 1)
typedef struct
{
uint8_t file_sig[16];
uint8_t file_ver[2];
uint16_t medium_type;
uint16_t sess_num;
uint16_t pad[2];
uint16_t bca_data_len;
uint32_t pad0[2];
uint32_t bca_data_offs_offs;
uint32_t pad1[6];
uint32_t disc_struct_offs;
uint32_t pad2[3];
uint32_t sess_blocks_offs;
uint32_t dpm_blocks_offs;
} mds_hdr_t; /* 88 bytes */
typedef struct
{
int32_t sess_start;
int32_t sess_end;
uint16_t sess_id;
uint8_t all_blocks_num;
uint8_t non_track_blocks_num;
uint16_t first_trk;
uint16_t last_trk;
uint32_t pad;
uint32_t trk_blocks_offs;
} mds_sess_block_t; /* 24 bytes */
typedef struct
{
uint8_t trk_mode;
/* DiscImageCreator says this is the number of subchannels. */
uint8_t subch_mode;
uint8_t adr_ctl;
uint8_t track_id;
uint8_t point;
uint8_t m;
uint8_t s;
uint8_t f;
uint8_t zero;
uint8_t pm;
uint8_t ps;
uint8_t pf;
/* DiscImageCreator calls this the index offset. */
uint32_t ex_offs;
uint16_t sector_len;
/* DiscImageCreator says unknown1 followed by 17x zero. */
uint8_t pad0[18];
uint32_t start_sect;
uint64_t start_offs;
uint32_t files_num;
uint32_t footer_offs;
uint8_t pad1[24];
} mds_trk_block_t; /* 80 bytes */
/*
DiscImageCreator's interpretation here makes sense and essentially
matches libmirage's - Index 0 sectors followed by Index 1 sectors.
*/
typedef struct
{
uint32_t pregap;
uint32_t trk_sectors;
} mds_trk_ex_block_t; /* 8 bytes */
typedef struct
{
uint32_t fn_offs;
uint32_t fn_is_wide;
uint32_t pad;
uint32_t pad0;
} mds_footer_t; /* 16 bytes */
typedef struct
{
uint32_t type;
uint32_t pad[2];
uint32_t entries;
} mds_dpm_block_t;
#pragma pack(pop)
#ifdef ENABLE_IMAGE_LOG
int image_do_log = ENABLE_IMAGE_LOG;
void
image_log(void *priv, const char *fmt, ...)
{
va_list ap;
if (image_do_log) {
va_start(ap, fmt);
log_out(priv, fmt, ap);
va_end(ap);
}
}
static char *cit[4] = { "SPECIAL", "NONE", "ZERO", "NORMAL" };
#else
# define image_log(priv, 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, const uint64_t seek, const size_t count)
{
const track_file_t *tf = (track_file_t *) priv;
const audio_file_t *audio = (audio_file_t *) tf->priv;
const uint64_t samples_seek = seek / 4;
const uint64_t samples_count = count / 4;
if ((seek & 3) || (count & 3)) {
image_log(tf->log, "CD Audio file: Reading on non-4-aligned boundaries.\n");
}
const 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)
{
const track_file_t *tf = (track_file_t *) priv;
const 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 uint8_t id, 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 == NULL) {
image_log(tf->log, "Audio file open error!");
goto cleanup_error;
}
if (audio->info.channels != 2 || audio->info.samplerate != 44100 || !audio->info.seekable) {
image_log(tf->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;
char n[1024] = { 0 };
sprintf(n, "CD-ROM %i Audio", id + 1);
tf->log = log_open(n);
return tf;
cleanup_error:
free(tf);
free(audio);
*error = 1;
return NULL;
}
/* Binary file functions. */
static int
bin_read(void *priv, uint8_t *buffer, const uint64_t seek, const size_t count)
{
const track_file_t *tf = (track_file_t *) priv;
if (tf->fp == NULL)
return 0;
image_log(tf->log, "binary_read(%08lx, pos=%" PRIu64 " count=%lu)\n",
tf->fp, seek, count);
if (fseeko64(tf->fp, seek, SEEK_SET) == -1) {
image_log(tf->log, "binary_read failed during seek!\n");
return -1;
}
if (fread(buffer, count, 1, tf->fp) != 1) {
image_log(tf->log, "binary_read failed during read!\n");
return -1;
}
if (UNLIKELY(tf->motorola)) {
for (uint64_t i = 0; i < count; i += 2) {
const uint8_t buffer0 = buffer[i];
const uint8_t buffer1 = buffer[i + 1];
buffer[i] = buffer1;
buffer[i + 1] = buffer0;
}
}
return 1;
}
static uint64_t
bin_get_length(void *priv)
{
const track_file_t *tf = (track_file_t *) priv;
if (tf->fp == NULL)
return 0;
fseeko64(tf->fp, 0, SEEK_END);
const off64_t len = ftello64(tf->fp);
image_log(tf->log, "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));
log_close(tf->log);
tf->log = NULL;
free(priv);
}
static track_file_t *
bin_init(const uint8_t id, 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;
}
char n[1024] = { 0 };
sprintf(n, "CD-ROM %i Bin ", id + 1);
tf->log = log_open(n);
memset(tf->fn, 0x00, sizeof(tf->fn));
strncpy(tf->fn, filename, sizeof(tf->fn) - 1);
tf->fp = plat_fopen64(tf->fn, "rb");
image_log(tf->log, "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 {
log_close(tf->log);
tf->log = NULL;
free(tf);
}
tf = NULL;
}
return tf;
}
static track_file_t *
index_file_init(const uint8_t id, const char *filename, int *error, int *is_viso)
{
track_file_t *tf = NULL;
*is_viso = 0;
/* Current we only support .BIN files, either combined or one per
track. In the future, more is planned. */
tf = bin_init(id, filename, error);
if (*error) {
if ((tf != NULL) && (tf->close != NULL)) {
tf->close(tf);
tf = NULL;
}
tf = viso_init(id, filename, error);
if (!*error)
*is_viso = 1;
}
return tf;
}
static void
index_file_close(track_index_t *idx)
{
if ((idx == NULL) || (idx->file == NULL))
return;
image_log(idx->file->log, "Log closed\n");
if (idx->file->log != NULL) {
log_close(idx->file->log);
idx->file->log = NULL;
}
if (idx->file->close != NULL)
idx->file->close(idx->file);
idx->file = NULL;
}
/* Internal functions. */
static int
image_get_track(const cd_image_t *img, const uint32_t sector)
{
int ret = -1;
for (int i = 0; i < img->tracks_num; i++) {
track_t *ct = &(img->tracks[i]);
for (int j = 0; j < 3; j++) {
const track_index_t *ci = &(ct->idx[j]);
if ((ci->type >= INDEX_ZERO) && (ci->length != 0ULL) &&
((sector + 150) >= ci->start) && ((sector + 150) <= (ci->start + ci->length - 1))) {
ret = i;
break;
}
}
}
return ret;
}
static void
image_get_track_and_index(const cd_image_t *img, const uint32_t sector,
int *track, int *index)
{
*track = -1;
*index = -1;
for (int i = 0; i < img->tracks_num; i++) {
track_t *ct = &(img->tracks[i]);
if ((ct->point >= 1) && (ct->point <= 99)) for (int j = 0; j < 3; j++) {
track_index_t *ci = &(ct->idx[j]);
if ((ci->type >= INDEX_ZERO) && (ci->length != 0ULL) &&
((sector + 150) >= ci->start) && ((sector + 150) <= (ci->start + ci->length - 1))) {
*track = i;
*index = j;
break;
}
}
}
}
static int
image_is_sector_bad(const cd_image_t *img, const uint32_t sector)
{
int ret = 0;
if (img->bad_sectors_num > 0) for (int i = 0; i < img->bad_sectors_num; i++)
if (img->bad_sectors[i] == sector) {
ret = 1;
break;
}
return ret;
}
static int
image_is_track_audio(const cd_image_t *img, const uint32_t pos)
{
int ret = 0;
if (img->has_audio) {
const int track = image_get_track(img, pos);
if (track >= 0) {
const track_t *trk = &(img->tracks[track]);
ret = (trk->mode == 0);
}
}
return ret;
}
static int
image_can_read_pvd(track_file_t *file, const uint64_t start,
const uint64_t sector_size, const int xa)
{
uint8_t buf[2448] = { 0 };
/* First VD is located at sector 16. */
uint64_t seek = start + (16ULL * sector_size);
uint8_t *pvd = (uint8_t *) buf;
if (sector_size >= RAW_SECTOR_SIZE) {
if (xa)
pvd = &(buf[24]);
else
pvd = &(buf[16]);
} else if (sector_size >= 2332) {
if (xa)
pvd = &(buf[8]);
}
file->read(file, buf, seek, sector_size);
int ret = (((pvd[0] == 1) &&
!strncmp((char *) &(pvd[1]), "CD001", 5) &&
(pvd[6] == 1)) ||
((pvd[8] == 1) &&
!strncmp((char *) &(pvd[9]), "CDROM", 5) &&
(pvd[14] == 1)));
if (ret) {
if (sector_size >= RAW_SECTOR_SIZE) {
if (xa)
/* Mode 2 XA, Form from the sub-header. */
ret = 0x20 | (((buf[18] & 0x20) >> 5) + 1);
else
/* Mode from header. */
ret = buf[15] << 4;
} else if (sector_size >= 2332) {
if (xa)
/* Mode 2 XA, Form from the sub-header. */
ret = 0x20 | (((buf[2] & 0x20) >> 5) + 1);
else
/* Mode 2 non-XA. */
ret = 0x20;
} else if (sector_size >= 2324)
/* Mode 2 XA Form 2. */
ret = 0x22;
else if (!strncmp((char *) &(pvd[0x400]), "CD-XA001", 8))
/* Mode 2 XA Form 1. */
ret = 0x21;
else
/* Mode 1. */
ret = 0x10;
}
return ret;
}
static int
image_cue_get_buffer(char *str, char **line, const 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
image_cue_get_keyword(char **dest, char **line)
{
const int success = image_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
image_cue_get_number(char **line)
{
char temp[128];
uint64_t num;
if (!image_cue_get_buffer(temp, line, 0))
return 0;
if (sscanf(temp, "%" PRIu64, &num) != 1)
return 0;
return num;
}
static int
image_cue_get_frame(uint64_t *frames, char **line)
{
char temp[128];
int min = 0;
int sec = 0;
int fr = 0;
int success = image_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
image_cue_get_flags(track_t *cur, char **line)
{
char temp[128];
char temp2[128];
int success = image_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 *
image_insert_track(cd_image_t *img, const uint8_t session, const uint8_t point)
{
track_t *ct = NULL;
img->tracks_num++;
if (img->tracks == NULL) {
img->tracks = calloc(1, sizeof(track_t));
ct = &(img->tracks[0]);
} else {
img->tracks = realloc(img->tracks, img->tracks_num * sizeof(track_t));
ct = &(img->tracks[img->tracks_num - 1]);
}
image_log(img->log, " [TRACK ] Insert %02X: img->tracks[%2i]\n",
point, img->tracks_num - 1);
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;
if (point >= 0xb0)
ct->attr = 0x50;
return ct;
}
static void
image_process(cd_image_t *img)
{
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 pos = 0;
int ls = 0;
int map[256] = { 0 };
int lead[3] = { 0 };
uint64_t spg[256] = { 0ULL };
track_t *lo[256] = { 0 };
/*
Pass 2 - adjusting pre-gaps of the first track of every session and creating the
map so we can map from <A0/A1/A2> <01-99> <B0/C0> to <01-99> <A0/A1/A2> <B0/C0>
so that their times and length can be adjusted correctly in the third and fourth
passes - especially important for multi-session Cue files.
We have to do that because Cue sheets do not explicitly indicate those pre-gaps
but they are required so we have the correct frames - the first track of each
session always has a pre-gap of at least 0:02:00. We do not adjust it if it is
already present.
*/
image_log(img->log, "Pass 2 (adjusting pre-gaps and preparing map)...\n");
/* Pre-gap of the first track of the first session. */
ct = &(img->tracks[3]);
ci = &(ct->idx[0]);
if (ci->type == INDEX_NONE) {
ci->type = INDEX_ZERO;
ci->start = 0ULL;
ci->length = 150ULL;
}
image_log(img->log, " [PREGAP ] Adjusted pre-gap of track %02X (first in "
"session %i)\n", ct->point, ct->session);
/* The other pre-gaps and the preparation of the map. */
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[i]);
if (((ct->point >= 1) && (ct->point <= 99)) || (ct->point >= 0xb0)) {
if (ct->point == 0xb0) {
/* The first track of a session always has a pre-gap of at least 0:02:00. */
track_t *ft = &(img->tracks[i + (ct->session == 1) + 4]);
ci = &(ft->idx[0]);
if (ci->type == INDEX_NONE) {
if (ft->idx[1].type == INDEX_NORMAL) {
ci->type = INDEX_NORMAL;
ci->file_start = ft->idx[1].file_start - 150ULL;
} else {
ci->type = INDEX_ZERO;
ci->start = 0ULL;
ci->length = 150ULL;
}
}
image_log(img->log, " [PREGAP ] Adjusted pre-gap of track %02X "
"(first in session %i)\n", ft->point, ct->session);
/* Point B0h found, add the previous three lead tracks. */
for (int j = 0; j < 3; j++) {
map[pos] = lead[j];
image_log(img->log, " [REMAP ] Remap %3i to %3i (%02X)\n", pos,
map[pos], 0xa0 + j);
pos++;
}
}
/* Add the current track. */
map[pos] = i;
image_log(img->log, " [NORMAL ] Remap %3i to %3i\n", pos, map[pos]);
pos++;
} else if ((ct->point >= 0xa0) && (ct->point <= 0xa2)) {
/*
Collect lead track (A0 = first track in session, A1 = last track in session,
A2 = lead out).
*/
lead[ct->point & 0x03] = i;
image_log(img->log, " [LEAD ] Lead %i = %3i (%02X)\n", ct->point & 0x03, i,
ct->point);
}
}
/* Add the last three lead tracks. */
for (int i = 0; i < 3; i++) {
map[pos] = lead[i];
image_log(img->log, " [REMAP ] Remap %3i to %3i (%02X)\n", pos, map[pos],
0xa0 + i);
pos++;
}
/*
If these two mismatch, it is a fatal condition since it means something
has gone wrong enough that the Cue sheet processing has been messed up.
*/
if (pos != img->tracks_num)
log_fatal(img->log, "Something has gone wrong and we have remappped %3i tracks "
"instead of the expected %3i\n", pos, img->tracks_num);
/*
Pass 3 - adjusting the time lengths of each index of track according to the
files.
We have to do that because Cue sheets do not explicitly indicate the lengths
of track, so we have to deduce them from what the combination of the Cue sheet
and the various files give us.
*/
image_log(img->log, "Pass 3 (adjusting track file lengths according to the files)...\n");
for (int i = (img->tracks_num - 1); i >= 0; i--) {
ct = &(img->tracks[map[i]]);
if (ct->idx[1].type != INDEX_SPECIAL) {
for (int j = 2; j >= 0; j--) {
ci = &(ct->idx[j]);
/*
If the file is not NULL and is different from the previous file,
open it and read its length.
*/
if ((ci->file != NULL) && (ci->file != tf)) {
tf = ci->file;
if (tf != NULL) {
tf_len = tf->get_length(tf) / ct->sector_size;
image_log(img->log, " [FILE ] File length: %016"
PRIX64 " sectors\n", tf_len);
}
}
if ((ci->type < INDEX_SPECIAL) || (ci->type > INDEX_NORMAL)) {
image_log(img->log, " [TRACK ] %02X, INDEX %02X, ATTR %02X,\n",
ci->type, j,
ct->attr);
log_fatal(img->log, " Unrecognized index type during "
"Pass 3: %2i\n",
ci->type);
} 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;
} else {
/* Index was not in the cue sheet or is not present in the file,
keep its length at zero. */
ci->file_start = tf_len;
}
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, ATTR %02X, "
"MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
image_log(img->log, " file_start = %016"
PRIX64 " (%2i:%02i:%02i),\n",
ci->file_start,
(int) ((ci->file_start / 75) / 60),
(int) ((ci->file_start / 75) % 60),
(int) (ci->file_start % 75));
image_log(img->log, " file_length = %016"
PRIX64 " (%2i:%02i:%02i),\n",
ci->file_length,
(int) ((ci->file_length / 75) / 60),
(int) ((ci->file_length / 75) % 60),
(int) (ci->file_length % 75));
image_log(img->log, " remaining = %016"
PRIX64 " (%2i:%02i:%02i)\n",
tf_len,
(int) ((tf_len / 75) / 60),
(int) ((tf_len / 75) % 60),
(int) (tf_len % 75));
}
}
}
/*
Pass 4 - calculating the actual track starts and lengths for the TOC.
*/
image_log(img->log, "Pass 4 (calculating the actual track starts "
"and lengths for the TOC)...\n");
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[map[i]]);
if (ct->idx[1].type != INDEX_SPECIAL) {
int session_changed = 0;
/*
If the session has changed, store the last session
and mark that it has changed.
*/
if (ct->session != ls) {
ls = ct->session;
session_changed = 1;
}
for (int j = 0; j < 3; j++) {
ci = &(ct->idx[j]);
if ((ci->type < INDEX_SPECIAL) || (ci->type > INDEX_NORMAL)) {
image_log(img->log, " [TRACK ] %02X, INDEX %02X, ATTR %02X,\n",
ci->type, j,
ct->attr);
log_fatal(img->log, " Unrecognized index type during "
"Pass 4: %2i\n",
ci->type);
} else 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;
}
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, ATTR %02X, "
"MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
image_log(img->log, " start = %016"
PRIX64 " (%2i:%02i:%02i),\n",
ci->start,
(int) ((ci->start / 75) / 60),
(int) ((ci->start / 75) % 60),
(int) (ci->start % 75));
image_log(img->log, " length = %016"
PRIX64 " (%2i:%02i:%02i),\n",
ci->length,
(int) ((ci->length / 75) / 60),
(int) ((ci->length / 75) % 60),
(int) (ci->length % 75));
image_log(img->log, " cur_pos = %016"
PRIX64 " (%2i:%02i:%02i)\n",
cur_pos,
(int) ((cur_pos / 75) / 60),
(int) ((cur_pos / 75) % 60),
(int) (cur_pos % 75));
/* Set the pre-gap of the first track of this session. */
if (session_changed)
spg[ct->session] = ct->idx[0].start;
}
}
}
/*
Pass 5 - setting the lead out starts for all sessions.
*/
image_log(img->log, "Pass 5 (setting the lead out starts for all sessions)...\n");
for (int i = 0; i <= ls; i++) {
lo[i] = NULL;
for (int j = (img->tracks_num - 1); j >= 0; j--) {
const track_t *jt = &(img->tracks[j]);
if ((jt->session == i) && (jt->point >= 1) && (jt->point <= 99)) {
lo[i] = &(img->tracks[j]);
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, ATTR %02X, "
"MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
image_log(img->log, " using to calculate the start of session "
"%02X lead out\n",
ct->session);
break;
}
}
}
/*
Pass 6 - refinining modes and forms, and finalizing all the special tracks.
*/
image_log(img->log, "Pass 6 (refinining modes and forms, and finalizing "
"all the special tracks)...\n");
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[i]);
lt = NULL;
switch (ct->point) {
default:
break;
case 1 ... 99:
ci = &(ct->idx[1]);
if ((ci->type == INDEX_NORMAL) && (ct->mode >= 1)) {
image_log(img->log, " [TRACK ] %02X/01, INDEX %02X, ATTR %02X, "
"MODE %02X/%02X, %8s,\n",
ct->session,
ct->point,
ct->attr,
ct->mode, ct->form,
cit[ct->idx[1].type + 2]);
/* Override the loaded modes with that we determine here. */
int can_read_pvd = image_can_read_pvd(ci->file,
ci->file_start * ct->sector_size,
ct->sector_size, 0);
ct->skip = 0;
if (can_read_pvd) {
ct->mode = can_read_pvd >> 4;
ct->form = can_read_pvd & 0xf;
if (((ct->sector_size == 2332) || (ct->sector_size == 2336)) &&
(ct->form >= 1))
ct->skip = 8;
} else if (ct->sector_size >= 2332) {
can_read_pvd = image_can_read_pvd(ci->file,
ci->file_start * ct->sector_size,
ct->sector_size, 1);
if (can_read_pvd) {
ct->mode = can_read_pvd >> 4;
ct->form = can_read_pvd & 0xf;
if (((ct->sector_size == 2332) || (ct->sector_size == 2336)) &&
(ct->form >= 1))
ct->skip = 8;
}
}
image_log(img->log, " NEW MODE: %02X/%02X\n",
ct->mode, ct->form);
}
break;
case 0xa0:
for (int j = 0; j < img->tracks_num; j++) {
track_t *jt = &(img->tracks[j]);
if ((jt->session == ct->session) &&
(jt->point >= 1) && (jt->point <= 99)) {
lt = jt;
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 = 0x20;
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;
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, "
"ATTR %02X, MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
image_log(img->log, " first track = %02X, "
"disc type = %02X\n",
lt->point, disc_type);
}
}
break;
case 0xa1:
for (int j = (img->tracks_num - 1); j >= 0; j--) {
track_t *jt = &(img->tracks[j]);
if ((jt->session == ct->session) && (jt->point >= 1) && (jt->point <= 99)) {
lt = jt;
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;
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, "
"ATTR %02X, MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
image_log(img->log, " last track = %02X\n",
lt->point);
}
}
break;
case 0xa2:
if (lo[ct->session] != NULL) {
/*
We have a track to use for the calculation, first adjust the track's
attribute (ADR/Ctrl), mode, and form to match the last non-special track.
*/
lt = lo[ct->session];
ct->attr = lt->attr;
ct->mode = lt->mode;
ct->form = lt->form;
if (ct->idx[1].type != INDEX_NORMAL) {
/*
Index not normal, therefore, this is not a lead out track from a
second or afterwards session of a multi-session Cue sheet, calculate
the starting time and update all the indexes accordingly.
*/
const track_index_t *li = &(lt->idx[2]);
for (int j = 0; j < 3; j++) {
image_log(img->log, " [TRACK ] %02X/%02X, INDEX %02X, "
"ATTR %02X, MODE %02X/%02X, %8s,\n",
ct->session,
ct->point, j,
ct->attr,
ct->mode, ct->form,
cit[ci->type + 2]);
ci = &(ct->idx[j]);
ci->type = INDEX_ZERO;
ci->start = li->start + li->length;
ci->length = 0;
image_log(img->log, " start = %016" PRIX64
" (%2i:%02i:%02i)\n",
ci->start,
(int) ((ci->start / 75) / 60),
(int) ((ci->start / 75) % 60),
(int) (ci->start % 75));
}
}
#ifdef ENABLE_IMAGE_LOG
else
image_log(img->log, " no start calculation done, "
"already specified\n");
#endif
}
#ifdef ENABLE_IMAGE_LOG
else
image_log(img->log, " nothing done, no suitable last track "
"found\n");
#endif
break;
case 0xb0:
/*
B0: MSF points to the beginning of the pre-gap
of the following session's first track.
*/
ct->extra[0] = (spg[ct->session + 1] / 75) / 60;
ct->extra[1] = (spg[ct->session + 1] / 75) % 60;
ct->extra[2] = spg[ct->session + 1] % 75;
image_log(img->log, " [TRACK ] %02X/%02X, INDEX 01, "
"ATTR %02X, MODE %02X/%02X, %8s,\n",
ct->session,
ct->point,
ct->attr,
ct->mode, ct->form,
cit[ct->idx[1].type + 2]);
image_log(img->log, " %02X:%02X:%02X, %02X,\n",
ct->extra[0], ct->extra[1], ct->extra[2], ct->extra[3]);
/*
B0 PMSF points to the start of the lead out track
of the last session.
*/
if (lo[ls] != NULL) {
lt = lo[ls];
const track_index_t *li = &(lt->idx[2]);
ct->idx[1].start = li->start + li->length;
image_log(img->log, " start = %016" PRIX64
" (%2i:%02i:%02i)\n",
ct->idx[1].start,
(int) ((ct->idx[1].start / 75) / 60),
(int) ((ct->idx[1].start / 75) % 60),
(int) (ct->idx[1].start % 75));
}
#ifdef ENABLE_IMAGE_LOG
else
image_log(img->log, " no start calculation done, "
"no suitable last track found\n");
#endif
break;
}
}
#ifdef ENABLE_IMAGE_LOG
image_log(img->log, "Final tracks list:\n");
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[i]);
for (int j = 0; j < 3; j++) {
ci = &(ct->idx[j]);
image_log(img->log, " [TRACK ] %02X INDEX %02X: [%8s, %016" PRIX64 "]\n",
ct->point, j,
cit[ci->type + 2], ci->file_start * ct->sector_size);
image_log(img->log, " TOC data: %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X %02X %02X\n",
ct->session, ct->attr, ct->tno, ct->point,
ct->extra[0], ct->extra[1], ct->extra[2], ct->extra[3],
(uint32_t) ((ci->start / 75) / 60),
(uint32_t) ((ci->start / 75) % 60),
(uint32_t) (ci->start % 75));
}
}
#endif
}
static void
image_set_track_subch_type(track_t *ct)
{
if (ct->sector_size == 2448)
ct->subch_type = 0x08;
else if (ct->sector_size == 2368)
ct->subch_type = 0x10;
else
ct->subch_type = 0x00;
}
static int
image_load_iso(cd_image_t *img, const char *filename)
{
track_t *ct = NULL;
track_index_t *ci = NULL;
track_file_t *tf = NULL;
int success = 1;
int error = 1;
int is_viso = 0;
int sector_sizes[8] = { 2448, 2368, RAW_SECTOR_SIZE, 2336,
2332, 2328, 2324, COOKED_SECTOR_SIZE };
img->tracks = NULL;
/*
Pass 1 - loading the ISO image.
*/
image_log(img->log, "Pass 1 (loading the ISO image)...\n");
img->tracks_num = 0;
image_insert_track(img, 1, 0xa0);
image_insert_track(img, 1, 0xa1);
image_insert_track(img, 1, 0xa2);
/* Data track (shouldn't there be a lead in track?). */
tf = index_file_init(img->dev->id, filename, &error, &is_viso);
if (error) {
if (tf != NULL) {
tf->close(tf);
tf = NULL;
}
success = 0;
} else if (is_viso)
success = 3;
if (success) {
ct = image_insert_track(img, 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 (int i = 0; i < 8; i++) {
ct->sector_size = sector_sizes[i];
int can_read_pvd = image_can_read_pvd(ci->file, 0, ct->sector_size, 0);
if (can_read_pvd) {
ct->mode = can_read_pvd >> 4;
ct->form = can_read_pvd & 0xf;
if (((ct->sector_size == 2332) || (ct->sector_size == 2336)) &&
(ct->form >= 1))
ct->skip = 8;
break;
} else if (ct->sector_size >= 2332) {
can_read_pvd = image_can_read_pvd(ci->file, 0, ct->sector_size, 1);
if (can_read_pvd) {
ct->mode = can_read_pvd >> 4;
ct->form = can_read_pvd & 0xf;
if (((ct->sector_size == 2332) || (ct->sector_size == 2336)) &&
(ct->form >= 1))
ct->skip = 8;
break;
}
}
}
image_set_track_subch_type(ct);
image_log(img->log, " [TRACK ] %02X/%02X, ATTR %02X, MODE %02X/%02X,\n",
ct->session,
ct->point,
ct->attr,
ct->mode, ct->form);
image_log(img->log, " %02X:%02X:%02X, %02X, %i\n",
ct->sector_size);
}
if (success) for (int i = 2; i >= 0; i--) {
if (ct->idx[i].file == NULL)
ct->idx[i].file = tf;
else
break;
}
tf = NULL;
if (success)
image_process(img);
else {
#ifdef ENABLE_IMAGE_LOG
log_warning(img->log, "Unable to open image or folder \"%s\"\n",
filename);
#else
warning("Unable to open image or folder \"%s\"\n", filename);
#endif
return 0;
}
return success;
}
static int
image_load_cue(cd_image_t *img, 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 last_t = -1;
int is_viso = 0;
int lead[3] = { 0 };
int error;
char pathname[MAX_FILENAME_LENGTH];
char buf[MAX_LINE_LENGTH];
char *line;
char *command;
char *type;
char temp;
img->tracks = NULL;
img->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. */
FILE *fp = plat_fopen(cuefile, "r");
if (fp == NULL)
return 0;
int success = 0;
/*
Pass 1 - loading the Cue sheet.
*/
image_log(img->log, "Pass 1 (loading the Cue sheet)...\n");
img->tracks_num = 0;
for (int i = 0; i < 3; i++) {
lead[i] = img->tracks_num;
(void) image_insert_track(img, session, 0xa0 + i);
}
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 */
}
}
image_log(img->log, " [LINE ] \"%s\"\n", line);
(void) image_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 = image_cue_get_buffer(ansi, &line, 0);
if (!success)
break;
success = image_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 = index_file_init(img->dev->id, 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(img->dev->id, filename, &error);
}
if (error) {
if (tf != NULL) {
tf->close(tf);
tf = NULL;
}
success = 0;
} else if (is_viso)
success = 3;
#ifdef ENABLE_IMAGE_LOG
if (!success)
image_log(img->log, " [FILE ] Unable to open file \"%s\" "
"specified in cue sheet\n", filename);
#endif
} else if (!strcmp(command, "TRACK")) {
int t = image_cue_get_number(&line);
success = image_cue_get_keyword(&type, &line);
if (!success)
break;
if (last_t != -1) {
/*
Important: This has to be done like this because pointers
change due to realloc.
*/
ct = &(img->tracks[img->tracks_num - 1]);
for (int i = 2; i >= 0; i--) {
if (ct->idx[i].file == NULL)
ct->idx[i].file = tf;
else
break;
}
}
last_t = t;
ct = image_insert_track(img, session, 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) {
default:
break;
case 2324: case 2328:
ct->form = 2;
break;
case 2048: case 2332: case 2336: case 2352: case 2368: case 2448:
ct->form = 1;
break;
}
if (((ct->sector_size == 2336) || (ct->sector_size == 2332)) && (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) {
image_set_track_subch_type(ct);
last = ct->sector_size;
image_log(img->log, " [TRACK ] %02X/%02X, ATTR %02X, MODE %02X/%02X,\n",
ct->session,
ct->point,
ct->attr,
ct->mode, ct->form);
image_log(img->log, " %i\n",
ct->sector_size);
}
#ifdef ENABLE_IMAGE_LOG
else
image_log(img->log, " [TRACK ] Unable to initialize track %02X "
"specified in Cue sheet\n", t);
#endif
} else if (!strcmp(command, "INDEX")) {
int t = image_cue_get_number(&line);
ci = &(ct->idx[t]);
ci->type = INDEX_NORMAL;
ci->file = tf;
success = image_cue_get_frame(&frame, &line);
ci->file_start = frame;
image_log(img->log, " [INDEX ] %02X (%8s): Initialization %s\n",
t, cit[ci->type + 2], success ? "successful" : "failed");
} else if (!strcmp(command, "PREGAP")) {
ci = &(ct->idx[0]);
ci->type = INDEX_ZERO;
ci->file = tf;
success = image_cue_get_frame(&frame, &line);
ci->length = frame;
image_log(img->log, " [INDEX ] 00 (%8s): Initialization %s\n",
cit[ci->type + 2], success ? "successful" : "failed");
} else if (!strcmp(command, "PAUSE") || !strcmp(command, "ZERO")) {
ci = &(ct->idx[1]);
ci->type = INDEX_ZERO;
ci->file = tf;
success = image_cue_get_frame(&frame, &line);
ci->length = frame;
image_log(img->log, " [INDEX ] 01 (%8s): Initialization %s\n",
cit[ci->type + 2], success ? "successful" : "failed");
} else if (!strcmp(command, "POSTGAP")) {
ci = &(ct->idx[2]);
ci->type = INDEX_ZERO;
ci->file = tf;
success = image_cue_get_frame(&frame, &line);
ci->length = frame;
image_log(img->log, " [INDEX ] 02 (%8s): Initialization %s\n",
cit[ci->type + 2], success ? "successful" : "failed");
} else if (!strcmp(command, "FLAGS")) {
success = image_cue_get_flags(ct, &line);
image_log(img->log, " [FLAGS ] Initialization %s\n",
success ? "successful" : "failed");
} else if (!strcmp(command, "REM")) {
success = 1;
char *space = strstr(line, " ");
if (space != NULL) {
space++;
if (space < (line + strlen(line))) {
(void) image_cue_get_keyword(&command, &space);
if (!strcmp(command, "LEAD-OUT")) {
ct = &(img->tracks[lead[2]]);
/*
Mark it this way so file pointers on it are not
going to be adjusted.
*/
last_t = -1;
ct->sector_size = last;
ci = &(ct->idx[1]);
ci->type = INDEX_NORMAL;
ci->file = tf;
success = image_cue_get_frame(&frame, &space);
ci->file_start = frame;
image_log(img->log, " [LEAD-OUT] Initialization %s\n",
success ? "successful" : "failed");
} else if (!strcmp(command, "SESSION")) {
session = image_cue_get_number(&space);
if (session > 1) {
ct = image_insert_track(img, session - 1, 0xb0);
/*
Mark it this way so file pointers on it are not
going to be adjusted.
*/
last_t = -1;
ci = &(ct->idx[1]);
ci->start = (0x40 * 60 * 75) + (0x02 * 75);
if (session == 2) {
ct->extra[3] = 0x02;
/*
00:00:00 on Wembley, C0:00:00 in the spec.
And what's in PMSF?
*/
ct = image_insert_track(img, session - 1, 0xc0);
/*
Mark it this way so file pointers on it are not
going to be adjusted.
*/
last_t = -1;
ci = &(ct->idx[1]);
/* Queen - Live at Wembley '86 CD 1. */
ci->start = (0x5f * 75 * 60);
/* Optimum recording power. */
ct->extra[0] = 0x00;
} else
ct->extra[3] = 0x01;
for (int i = 0; i < 3; i++) {
lead[i] = img->tracks_num;
(void) image_insert_track(img, session, 0xa0 + i);
}
}
image_log(img->log, " [SESSION ] Initialization successful\n");
}
}
}
} else if (!strcmp(command, "CATALOG") || !strcmp(command, "CDTEXTFILE") ||
!strcmp(command, "ISRC") || !strcmp(command, "PERFORMER") ||
!strcmp(command, "SONGWRITER") || !strcmp(command, "TITLE") ||
!strcmp(command, "")) {
/* Ignored commands. */
image_log(img->log, " [CUE ] Ignored command \"%s\" in Cue sheet\n",
command);
success = 1;
} else {
image_log(img->log, " [CUE ] Unsupported command \"%s\" in Cue sheet\n",
command);
success = 0;
}
if (!success)
break;
}
if (success && (ct != NULL)) for (int i = 2; i >= 0; i--) {
if (ct->idx[i].file == NULL)
ct->idx[i].file = tf;
else
break;
}
tf = NULL;
fclose(fp);
if (success)
image_process(img);
else
#ifdef ENABLE_IMAGE_LOG
log_warning(img->log, " [CUE ] Unable to open Cue sheet \"%s\"\n", cuefile);
#else
warning("Unable to open Cue sheet \"%s\"\n", cuefile);
#endif
return success;
}
// Converts UTF-16 string into UTF-8 string.
// If destination string is NULL returns total number of symbols that would've
// been written (without null terminator). However, when actually writing into
// destination string, it does include it. So, be sure to allocate extra byte
// for destination string.
// Params:
// u16_str - source UTF-16 string
// u16_str_len - length of source UTF-16 string
// u8_str - destination UTF-8 string
// u8_str_size - size of destination UTF-8 string in bytes
// Return value:
// 0 on success, -1 if encountered invalid surrogate pair, -2 if
// encountered buffer overflow or length of destination UTF-8 string in bytes
// (without including the null terminator).
long int utf16_to_utf8(const uint16_t *u16_str, size_t u16_str_len,
uint8_t *u8_str, size_t u8_str_size)
{
size_t i = 0, j = 0;
if (!u8_str) {
u8_str_size = u16_str_len * 4;
}
while (i < u16_str_len) {
uint32_t codepoint = u16_str[i++];
// check for surrogate pair
if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
uint16_t high_surr = codepoint;
uint16_t low_surr = u16_str[i++];
if (low_surr < 0xDC00 || low_surr > 0xDFFF)
return -1;
codepoint = ((high_surr - 0xD800) << 10) +
(low_surr - 0xDC00) + 0x10000;
}
if (codepoint < 0x80) {
if (j + 1 > u8_str_size) return -2;
if (u8_str) u8_str[j] = (char)codepoint;
j++;
} else if (codepoint < 0x800) {
if (j + 2 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xC0 | (codepoint >> 6);
u8_str[j + 1] = 0x80 | (codepoint & 0x3F);
}
j += 2;
} else if (codepoint < 0x10000) {
if (j + 3 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xE0 | (codepoint >> 12);
u8_str[j + 1] = 0x80 | ((codepoint >> 6) & 0x3F);
u8_str[j + 2] = 0x80 | (codepoint & 0x3F);
}
j += 3;
} else {
if (j + 4 > u8_str_size) return -2;
if (u8_str) {
u8_str[j + 0] = 0xF0 | (codepoint >> 18);
u8_str[j + 1] = 0x80 | ((codepoint >> 12) & 0x3F);
u8_str[j + 2] = 0x80 | ((codepoint >> 6) & 0x3F);
u8_str[j + 3] = 0x80 | (codepoint & 0x3F);
}
j += 4;
}
}
if (u8_str) {
if (j >= u8_str_size) return -2;
u8_str[j] = '\0';
}
return (long int)j;
}
static int
image_load_mds(cd_image_t *img, const char *mdsfile)
{
track_t *ct = NULL;
track_index_t *ci = NULL;
track_file_t *tf = NULL;
int is_viso = 0;
int last_t = -1;
int error;
char pathname[MAX_FILENAME_LENGTH];
char ofn[2048] = { 0 };
mds_hdr_t mds_hdr = { 0 };
mds_sess_block_t mds_sess_block = { 0 };
mds_trk_block_t mds_trk_block = { 0 };
mds_trk_ex_block_t mds_trk_ex_block = { 0 };
mds_footer_t mds_footer = { 0 };
mds_dpm_block_t mds_dpm_block = { 0 };
uint32_t mds_dpm_blocks_num = 0x00000000;
uint32_t mds_dpm_block_offs = 0x00000000;
img->tracks = NULL;
img->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, mdsfile);
/* Open the file. */
FILE *fp = plat_fopen(mdsfile, "rb");
if (fp == NULL)
return 0;
int success = 0;
/*
Pass 1 - loading the MDS sheet.
*/
image_log(img->log, "Pass 1 (loading the Media Descriptor Sheet)...\n");
img->tracks_num = 0;
success = 2;
fseek(fp, 0, SEEK_SET);
fread(&mds_hdr, 1, sizeof(mds_hdr_t), fp);
if (memcmp(mds_hdr.file_sig, "MEDIA DESCRIPTOR", 16)) {
#ifdef ENABLE_IMAGE_LOG
log_warning(img->log, " [MDS ] \"%s\"\n is not an actual MDF file",
mdsfile);
#else
warning("\"%s\"\n is not an actual MDF file", mdsfile);
#endif
fclose(fp);
return 0;
}
if (mds_hdr.file_ver[0] == 0x02) {
#ifdef ENABLE_IMAGE_LOG
log_warning(img->log, " [MDS ] \"%s\" is a Daemon Tools encrypted MDS which is not supported\n",
mdsfile);
#else
warning("\"%s\" is a Daemon Tools encrypted MDS which is not supported\n", mdsfile);
#endif
fclose(fp);
return 0;
}
img->is_dvd = (mds_hdr.medium_type >= 0x10);
if (img->is_dvd) {
if (mds_hdr.disc_struct_offs != 0x00) {
fseek(fp, mds_hdr.disc_struct_offs, SEEK_SET);
fread(&(img->dstruct.layers[0]), 1, sizeof(layer_t), fp);
img->has_dstruct = 1;
if (((img->dstruct.layers[0].f0[2] & 0x60) >> 4) == 0x01) {
fseek(fp, mds_hdr.disc_struct_offs, SEEK_SET);
fread(&(img->dstruct.layers[1]), 1, sizeof(layer_t), fp);
img->has_dstruct++;
}
}
for (int t = 0; t < 3; t++) {
ct = image_insert_track(img, 1, 0xa0 + t);
ct->attr = DATA_TRACK;
ct->mode = 0;
ct->form = 0;
ct->tno = 0;
ct->subch_type = 0;
memset(ct->extra, 0x00, 4);
for (int i = 0; i < 3; i++) {
ci = &(ct->idx[i]);
ci->type = INDEX_NONE;
ci->start = 0;
ci->length = 0;
ci->file_start = 0;
ci->file_length = 0;
ci->file = NULL;
}
ci = &(ct->idx[1]);
if (t < 2)
ci->start = (0x01 * 60 * 75) + (0 * 75) + 0;
}
}
if (mds_hdr.dpm_blocks_offs != 0x00) {
fseek(fp, mds_hdr.dpm_blocks_offs, SEEK_SET);
fread(&mds_dpm_blocks_num, 1, sizeof(uint32_t), fp);
if (mds_dpm_blocks_num > 0) for (int b = 0; b < mds_dpm_blocks_num; b++) {
fseek(fp, mds_hdr.dpm_blocks_offs + 4 + (b * 4), SEEK_SET);
fread(&mds_dpm_block_offs, 1, sizeof(uint32_t), fp);
fseek(fp, mds_dpm_block_offs, SEEK_SET);
fread(&mds_dpm_block, 1, sizeof(mds_dpm_block_t), fp);
/* We currently only support the bad sectors block and not (yet) actual DPM. */
if (mds_dpm_block.type == 0x00000002) {
/* Bad sectors. */
img->bad_sectors_num = mds_dpm_block.entries;
img->bad_sectors = (uint32_t *) malloc(img->bad_sectors_num * sizeof(uint32_t));
fseek(fp, mds_dpm_block_offs + sizeof(mds_dpm_block_t), SEEK_SET);
fread(img->bad_sectors, 1, img->bad_sectors_num * sizeof(uint32_t), fp);
break;
}
}
}
for (int s = 0; s < mds_hdr.sess_num; s++) {
fseek(fp, mds_hdr.sess_blocks_offs + (s * sizeof(mds_sess_block_t)), SEEK_SET);
fread(&mds_sess_block, 1, sizeof(mds_sess_block_t), fp);
for (int t = 0; t < mds_sess_block.all_blocks_num; t++) {
fseek(fp, mds_sess_block.trk_blocks_offs + (t * sizeof(mds_trk_block_t)), SEEK_SET);
fread(&mds_trk_block, 1, sizeof(mds_trk_block_t), fp);
if (last_t != -1) {
/*
Important: This has to be done like this because pointers
change due to realloc.
*/
ct = &(img->tracks[img->tracks_num - 1]);
for (int i = 2; i >= 0; i--) {
if (ct->idx[i].file == NULL)
ct->idx[i].file = tf;
else
break;
}
}
last_t = mds_trk_block.point;
ct = image_insert_track(img, mds_sess_block.sess_id, mds_trk_block.point);
if (img->is_dvd) {
/* DVD images have no extra block - the extra block offset is the track length. */
memset(&mds_trk_ex_block, 0x00, sizeof(mds_trk_ex_block_t));
mds_trk_ex_block.pregap = 0x00000000;
mds_trk_ex_block.trk_sectors = mds_trk_block.ex_offs;
} else if (mds_trk_block.ex_offs != 0ULL) {
fseek(fp, mds_trk_block.ex_offs, SEEK_SET);
fread(&mds_trk_ex_block, 1, sizeof(mds_trk_ex_block), fp);
}
uint32_t astart = mds_trk_block.start_sect - mds_trk_ex_block.pregap;
uint32_t aend = astart + mds_trk_ex_block.pregap;
uint32_t aend2 = aend + mds_trk_ex_block.trk_sectors;
uint32_t astart2 = mds_trk_block.start_sect + mds_trk_ex_block.trk_sectors;
if (mds_trk_block.footer_offs != 0ULL) for (uint32_t ff = 0; ff < mds_trk_block.files_num; ff++) {
fseek(fp, mds_trk_block.footer_offs + (ff * sizeof(mds_footer_t)), SEEK_SET);
fread(&mds_footer, 1, sizeof(mds_footer_t), fp);
uint16_t wfn[2048] = { 0 };
char fn[2048] = { 0 };
fseek(fp, mds_footer.fn_offs, SEEK_SET);
if (mds_footer.fn_is_wide) {
int len = 0;
for (int i = 0; i < 256; i++) {
fread(&(wfn[i]), 1, 2, fp);
len++;
if (wfn[i] == 0x0000)
break;
}
(void) utf16_to_utf8(wfn, 2048, (uint8_t *) fn, 2048);
} else for (int i = 0; i < 512; i++) {
fread(&fn[i], 1, 1, fp);
if (fn[i] == 0x00)
break;
}
if (!stricmp(fn, "*.mdf")) {
strcpy(fn, mdsfile);
fn[strlen(mdsfile) - 3] = 'm';
fn[strlen(mdsfile) - 2] = 'd';
fn[strlen(mdsfile) - 1] = 'f';
}
char filename[2048] = { 0 };
if (!path_abs(fn))
path_append_filename(filename, pathname, fn);
else
strcpy(filename, fn);
if (strcmp(ofn, filename) != 0) {
tf = index_file_init(img->dev->id, filename, &error, &is_viso);
strcpy(ofn, filename);
}
}
ct->sector_size = mds_trk_block.sector_len;
ct->form = 0;
ct->tno = mds_trk_block.track_id;
ct->subch_type = mds_trk_block.subch_mode;
ct->extra[0] = mds_trk_block.m;
ct->extra[1] = mds_trk_block.s;
ct->extra[2] = mds_trk_block.f;
ct->extra[3] = mds_trk_block.zero;
/*
Note from DiscImageCreator:
I hexedited the track mode field with various values and fed it to Alchohol;
it seemed that high part of byte had no effect at all; only the lower one
affected the mode, in the following manner:
00: Mode 2, 01: Audio, 02: Mode 1, 03: Mode 2, 04: Mode 2 Form 1,
05: Mode 2 Form 2, 06: UKNONOWN, 07: Mode 2
08: Mode 2, 09: Audio, 0A: Mode 1, 0B: Mode 2, 0C: Mode 2 Form 1,
0D: Mode 2 Form 2, 0E: UKNONOWN, 0F: Mode 2
*/
ct->attr = ((mds_trk_block.trk_mode & 0x07) == 0x01) ?
AUDIO_TRACK : DATA_TRACK;
ct->mode = 0;
ct->form = 0;
if (((mds_trk_block.trk_mode & 0x07) != 0x01) &&
((mds_trk_block.trk_mode & 0x07) != 0x06))
ct->mode = ((mds_trk_block.trk_mode & 0x07) != 0x02) + 1;
if ((mds_trk_block.trk_mode & 0x06) == 0x04)
ct->form = (mds_trk_block.trk_mode & 0x07) - 0x03;
if (ct->attr == AUDIO_TRACK)
success = 1;
if (((ct->sector_size == 2336) || (ct->sector_size == 2332)) && (ct->mode == 2) && (ct->form == 1))
ct->skip = 8;
ci = &(ct->idx[0]);
if (ct->point < 0xa0) {
ci->start = astart + 150;
ci->length = mds_trk_ex_block.pregap;
}
ci->type = (ci->length > 0) ? INDEX_ZERO : INDEX_NONE;
ci->file_start = 0;
ci->file_length = 0;
ci->file = NULL;
ci = &(ct->idx[1]);
if ((mds_trk_block.point >= 1) && (mds_trk_block.point <= 99)) {
ci->start = aend + 150;
ci->length = mds_trk_ex_block.trk_sectors;
ci->type = INDEX_NORMAL;
ci->file_start = mds_trk_block.start_offs / ct->sector_size;
ci->file_length = ci->length;
ci->file = tf;
} else {
ci->start = (mds_trk_block.pm * 60 * 75) + (mds_trk_block.ps * 75) + mds_trk_block.pf;
ci->type = INDEX_NONE;
ci->file_start = 0;
ci->file_length = 0;
ci->file = NULL;
}
ci = &(ct->idx[2]);
if (ct->point < 0xa0) {
ci->start = aend2 + 150;
ci->length = astart2 - aend2;
}
ci->type = (ci->length > 0) ? INDEX_ZERO : INDEX_NONE;
ci->file_start = 0;
ci->file_length = 0;
ci->file = NULL;
if (img->is_dvd) {
ci = &(ct->idx[1]);
uint32_t total = ci->start + ci->length;
ci = &(img->tracks[2].idx[1]);
ci->start = total;
}
}
for (int i = 2; i >= 0; i--) {
if (ct->point >= 0xa0)
ci->type = INDEX_SPECIAL;
if (ct->idx[i].file == NULL)
ct->idx[i].file = tf;
else
break;
}
}
tf = NULL;
fclose(fp);
if (success) {
#ifdef ENABLE_IMAGE_LOG
image_log(img->log, "Final tracks list:\n");
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[i]);
for (int j = 0; j < 3; j++) {
ci = &(ct->idx[j]);
image_log(img->log, " [TRACK ] %02X INDEX %02X: [%8s, %016" PRIX64 "]\n",
ct->point, j,
cit[ci->type + 2], ci->file_start * ct->sector_size);
image_log(img->log, " TOC data: %02X %02X %02X "
"%02X %02X %02X %02X %02X %02X %02X %02X\n",
ct->session, ct->attr, ct->tno, ct->point,
ct->extra[0], ct->extra[1], ct->extra[2], ct->extra[3],
(uint32_t) ((ci->start / 75) / 60),
(uint32_t) ((ci->start / 75) % 60),
(uint32_t) (ci->start % 75));
}
}
#endif
} else
#ifdef ENABLE_IMAGE_LOG
log_warning(img->log, " [MDS ] Unable to open MDS sheet \"%s\"\n", mdsfile);
#else
warning("Unable to open MDS sheet \"%s\"\n", mdsfile);
#endif
return success;
}
/* Root functions. */
static void
image_clear_tracks(cd_image_t *img)
{
const track_file_t *last = NULL;
track_t *cur = NULL;
track_index_t *idx = NULL;
if ((img->tracks != NULL) && (img->tracks_num > 0)) {
for (int i = 0; i < img->tracks_num; i++) {
cur = &img->tracks[i];
if (((cur->point >= 1) && (cur->point <= 99)) ||
(cur->point == 0xa2)) 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) && (idx->type == INDEX_NORMAL)) {
if (idx->file != last) {
last = idx->file;
index_file_close(idx);
} else
idx->file = NULL;
}
}
}
/* Now free the array. */
free(img->tracks);
img->tracks = NULL;
/* Mark that there's no tracks. */
img->tracks_num = 0;
}
}
/* Shared functions. */
static int
image_get_track_info(const void *local, const uint32_t track,
const int end, track_info_t *ti)
{
const cd_image_t *img = (const cd_image_t *) local;
const track_t *ct = NULL;
int ret = 0;
for (int i = 0; i < img->tracks_num; i++) {
ct = &(img->tracks[i]);
if (ct->point == track)
break;
}
if (ct != NULL) {
const uint32_t pos = end ? (ct->idx[1].start + ct->idx[1].length) :
ct->idx[1].start;
ti->number = ct->point;
ti->attr = ct->attr;
ti->m = (pos / 75) / 60;
ti->s = (pos / 75) % 60;
ti->f = pos % 75;
ret = 1;
}
return ret;
}
static void
image_get_raw_track_info(const void *local, int *num, uint8_t *buffer)
{
const cd_image_t *img = (const cd_image_t *) local;
int len = 0;
image_log(img->log, "img->tracks_num = %i\n", img->tracks_num);
for (int i = 0; i < img->tracks_num; i++) {
const track_t *ct = &(img->tracks[i]);
#ifdef ENABLE_IMAGE_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 (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;
image_log(img->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 = img->tracks_num;
}
static int
image_is_track_pre(const void *local, const uint32_t sector)
{
const cd_image_t *img = (const cd_image_t *) local;
int ret = 0;
if (img->has_audio) {
const int track = image_get_track(img, sector);
if (track >= 0) {
const track_t *trk = &(img->tracks[track]);
ret = !!(trk->attr & 0x01);
}
}
return ret;
}
static int
image_read_sector(const void *local, uint8_t *buffer,
const uint32_t sector)
{
const cd_image_t *img = (const cd_image_t *) local;
cdrom_t *dev = (cdrom_t *) img->dev;
int m = 0;
int s = 0;
int f = 0;
int ret = 0;
uint32_t lba = sector;
int track;
int index;
uint8_t q[16] = { 0x00 };
uint8_t *buf = buffer;
if (sector == 0xffffffff)
lba = img->dev->seek_pos;
const uint64_t sect = (uint64_t) lba;
image_get_track_and_index(img, lba, &track, &index);
const track_t *trk = &(img->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) + trk->skip;
if (track >= 0) {
/* Signal CIRC error to the guest if sector is bad. */
ret = image_is_sector_bad(img, lba) ? -1 : 1;
if (ret > 0) {
uint64_t offset = 0ULL;
image_log(img->log, "cdrom_read_sector(%08X): track %02X, index %02X, %016"
PRIX64 ", %i, %i, %i, %i\n",
lba, track, index, idx->start, trk->sector_size, track_is_raw,
trk->mode, trk->form);
memset(buffer, 0x00, 2448);
if ((trk->attr & 0x04) && ((idx->type < INDEX_NORMAL) || !track_is_raw)) {
offset += 16ULL;
/* Construct the header. */
memset(buffer + 1, 0xff, 10);
buffer += 12;
FRAMES_TO_MSF(sector + 150, &m, &s, &f);
/* These have to be BCD. */
buffer[0] = bin2bcd(m & 0xff);
buffer[1] = bin2bcd(s & 0xff);
buffer[2] = bin2bcd(f & 0xff);
/* Data, should reflect the actual sector type. */
buffer[3] = trk->mode;
buffer += 4;
if (trk->form >= 1) {
offset += 8ULL;
/* Construct the CD-I/XA sub-header. */
buffer[2] = buffer[6] = (trk->form - 1) << 5;
buffer += 8;
}
}
if (idx->type >= INDEX_NORMAL) {
/* Read the data from the file. */
ret = idx->file->read(idx->file, buffer, seek, trk->sector_size);
} else
/* Index is not in the file, no read to fail here. */
ret = 1;
if ((ret > 0) && (trk->attr & 0x04) && ((idx->type < INDEX_NORMAL) || !track_is_raw)) {
uint32_t crc;
if ((trk->mode == 2) && (trk->form == 1)) {
crc = cdrom_crc32(0xffffffff, &(buf[16]), 2056) ^ 0xffffffff;
memcpy(&(buf[2072]), &crc, 4);
} else {
crc = cdrom_crc32(0xffffffff, buf, 2064) ^ 0xffffffff;
memcpy(&(buf[2064]), &crc, 4);
}
int m2f1 = (trk->mode == 2) && (trk->form == 1);
/* Compute ECC P code. */
cdrom_compute_ecc_block(dev, &(buf[2076]), &(buf[12]), 86, 24, 2, 86, m2f1);
/* Compute ECC Q code. */
cdrom_compute_ecc_block(dev, &(buf[2248]), &(buf[12]), 52, 43, 86, 88, m2f1);
}
if ((ret > 0) && ((idx->type < INDEX_NORMAL) || (trk->subch_type != 0x08))) {
buffer -= offset;
if (trk->subch_type == 0x10)
memcpy(q, &(buffer[2352]), 12);
else {
/* 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 - (lba + 150 - idx->start)), &m, &s, &f);
} else {
FRAMES_TO_MSF((int32_t) (lba + 150 - idx->start), &m, &s, &f);
}
q[3] = bin2bcd(m & 0xff);
q[4] = bin2bcd(s & 0xff);
q[5] = bin2bcd(f & 0xff);
FRAMES_TO_MSF(lba + 150, &m, &s, &f);
q[7] = bin2bcd(m & 0xff);
q[8] = bin2bcd(s & 0xff);
q[9] = bin2bcd(f & 0xff);
}
/* 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;
}
static uint8_t
image_get_track_type(const void *local, const uint32_t sector)
{
const cd_image_t *img = (cd_image_t *) local;
const int track = image_get_track(img, sector);
const track_t * trk = &(img->tracks[track]);
int ret = 0x00;
if (image_is_track_audio(img, sector))
ret = CD_TRACK_AUDIO;
else if (track >= 0) for (int i = 0; i < img->tracks_num; i++) {
const track_t *ct = &(img->tracks[i]);
const track_t *nt = &(img->tracks[i + 1]);
if (ct->point == 0xa0) {
const uint8_t first = (ct->idx[1].start / 75 / 60);
const uint8_t last = (nt->idx[1].start / 75 / 60);
if ((trk->point >= first) && (trk->point <= last)) {
ret = (ct->idx[1].start / 75) % 60;
break;
}
}
}
return ret;
}
static uint32_t
image_get_last_block(const void *local)
{
const cd_image_t *img = (const cd_image_t *) local;
uint32_t lb = 0x00000000;
if (img != NULL) {
const track_t *lo = NULL;
for (int i = (img->tracks_num - 1); i >= 0; i--) {
if (img->tracks[i].point == 0xa2) {
lo = &(img->tracks[i]);
break;
}
}
if (lo != NULL)
lb = lo->idx[1].start - 1;
}
return lb;
}
static int
image_read_dvd_structure(const void *local, const uint8_t layer, const uint8_t format,
uint8_t *buffer, uint32_t *info)
{
const cd_image_t *img = (const cd_image_t *) local;
int ret = 0;
if ((img->has_dstruct > 0) && ((layer + 1) > img->has_dstruct)) {
switch (format) {
case 0x00:
memcpy(buffer + 4, img->dstruct.layers[layer].f0, 2048);
ret = 2048 + 2;
break;
case 0x01:
memcpy(buffer + 4, img->dstruct.layers[layer].f1, 4);
ret = 4 + 2;
break;
case 0x04:
memcpy(buffer + 4, img->dstruct.layers[layer].f4, 2048);
ret = 2048 + 2;
break;
}
}
return ret;
}
static int
image_is_dvd(const void *local)
{
const cd_image_t *img = (const cd_image_t *) local;
return img->is_dvd;
}
static int
image_has_audio(const void *local)
{
const cd_image_t *img = (const cd_image_t *) local;
return img->has_audio;
}
static void
image_close(void *local)
{
cd_image_t *img = (cd_image_t *) local;
if (img != NULL) {
image_clear_tracks(img);
image_log(img->log, "Log closed\n");
log_close(img->log);
img->log = NULL;
if (img->bad_sectors != NULL)
free(img->bad_sectors);
free(img);
}
}
static const cdrom_ops_t image_ops = {
image_get_track_info,
image_get_raw_track_info,
image_is_track_pre,
image_read_sector,
image_get_track_type,
image_get_last_block,
image_read_dvd_structure,
image_is_dvd,
image_has_audio,
NULL,
image_close,
NULL
};
/* Public functions. */
void *
image_open(cdrom_t *dev, const char *path)
{
const uintptr_t ext = path + strlen(path) - strrchr(path, '.');
cd_image_t *img = (cd_image_t *) calloc(1, sizeof(cd_image_t));
if (img != NULL) {
int ret;
const int is_cue = ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "CUE"));
const int is_mds = ((ext == 4) && !stricmp(path + strlen(path) - ext + 1, "MDS"));
char n[1024] = { 0 };
sprintf(n, "CD-ROM %i Image", dev->id + 1);
img->log = log_open(n);
img->dev = dev;
if (is_mds) {
ret = image_load_mds(img, path);
if (ret >= 2)
img->has_audio = 0;
else if (ret)
img->has_audio = 1;
} else if (is_cue) {
ret = image_load_cue(img, path);
if (ret >= 2)
img->has_audio = 0;
else if (ret)
img->has_audio = 1;
if (ret >= 1)
img->is_dvd = 2;
} else {
ret = image_load_iso(img, path);
if (ret) {
img->has_audio = 0;
img->is_dvd = 2;
}
}
if (ret > 0) {
if (img->is_dvd == 2) {
uint32_t lb = image_get_last_block(img); /* Should be safer than previous way of doing it? */
img->is_dvd = (lb >= 524287); /* Minimum 1 GB total capacity as threshold for DVD. */
}
dev->ops = &image_ops;
} else {
log_warning(img->log, "Unable to load CD-ROM image: %s\n", path);
image_close(img);
img = NULL;
}
}
return img;
}