ref: 2b36a055e337aace2c8b31a27d2a4bc2e77d7b49
dir: /psx/dev/cdrom/cue.c/
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <ctype.h>
#include "cue.h"
static const char* cue_keywords[] = {
"4CH",
"AIFF",
"AUDIO",
"BINARY",
"CATALOG",
"CDG",
"CDI/2336",
"CDI/2352",
"CDTEXTFILE",
"DCP",
"FILE",
"FLAGS",
"INDEX",
"ISRC",
"MODE1/2048",
"MODE1/2352",
"MODE2/2336",
"MODE2/2352",
"MOTOROLA",
"MP3",
"PERFORMER",
"POSTGAP",
"PRE",
"PREGAP",
"REM",
"SCMS",
"SONGWRITER",
"TITLE",
"TRACK",
"WAVE",
0
};
char* strapp(char* dst, const char* a, const char* b) {
char* d = dst;
while (*a)
*dst++ = *a++;
while (*b)
*dst++ = *b++;
*dst = '\0';
return d;
}
const char* find_last_slash(const char* a) {
if (!a)
return NULL;
const char* b = a;
while (*a) {
if (*a == '/' || *a == '\\')
b = a + 1;
++a;
}
return b;
}
char* get_root_path(char* dst, const char* a) {
if (!a) {
*dst = '\0';
return dst;
}
const char* b = a;
const char* c = a;
char* d = dst;
while (*a) {
if (*a == '/' || *a == '\\')
b = a + 1;
++a;
}
while (c != b)
*dst++ = *c++;
*dst = '\0';
return d;
}
int cue_parse_keyword(cue_t* cue) {
char buf[256];
char* ptr = buf;
while (isalpha(cue->c) || isdigit(cue->c) || cue->c == '/') {
*ptr++ = cue->c;
cue->c = fgetc(cue->file);
}
*ptr = '\0';
int i = 0;
const char* keyword = cue_keywords[i];
while (keyword) {
if (!strcmp(keyword, buf)) {
return i;
} else {
keyword = cue_keywords[++i];
}
}
return -1;
}
int cue_parse_number(cue_t* cue) {
if (!isdigit(cue->c))
return 0;
char buf[4];
char* ptr = buf;
while (isdigit(cue->c)) {
*ptr++ = cue->c;
cue->c = fgetc(cue->file);
}
*ptr = '\0';
return atoi(buf);
}
uint32_t cue_parse_msf(cue_t* cue) {
int m = 0;
int s = 0;
int f = 0;
if (!isdigit(cue->c))
return 0;
m = cue_parse_number(cue);
if (cue->c != ':')
return 0;
cue->c = fgetc(cue->file);
s = cue_parse_number(cue);
if (cue->c != ':')
return 0;
cue->c = fgetc(cue->file);
f = cue_parse_number(cue);
// 1 second = 75 frames (sectors)
// 1 minute = 60 seconds = 4500 frames
return f + (s * 75) + (m * 4500);
}
void cue_parse_index(cue_t* cue) {
cue_track_t* track = list_back(cue->tracks)->data;
while (isspace(cue->c))
cue->c = fgetc(cue->file);
if (!isdigit(cue->c))
return;
int i = cue_parse_number(cue);
while (isspace(cue->c))
cue->c = fgetc(cue->file);
if (i > 1)
return;
track->index[i] = cue_parse_msf(cue);
}
cue_track_t* cue_parse_track(cue_t* cue) {
while (isspace(cue->c))
cue->c = fgetc(cue->file);
if (!isdigit(cue->c))
return NULL;
cue_track_t* track = malloc(sizeof(cue_track_t));
track->end = 0;
track->start = 0;
track->pregap = 0;
track->index[0] = -1;
track->index[1] = -1;
track->file = list_back(cue->files)->data;
track->number = cue_parse_number(cue);
while (isspace(cue->c))
cue->c = fgetc(cue->file);
track->mode = cue_parse_keyword(cue);
return track;
}
cue_file_t* cue_parse_file(cue_t* cue, const char* p, const char* s) {
while (isspace(cue->c))
cue->c = fgetc(cue->file);
if (cue->c != '\"')
return NULL;
cue_file_t* file = malloc(sizeof(cue_file_t));
file->tracks = list_create();
file->name = malloc(512);
// Append root path to track file path
char* ptr = file->name;
while (p != s)
*ptr++ = *p++;
cue->c = fgetc(cue->file);
while (cue->c != '\"') {
*ptr++ = cue->c;
cue->c = fgetc(cue->file);
}
*ptr = '\0';
cue->c = fgetc(cue->file);
// Ignore file type
while (isspace(cue->c))
cue->c = fgetc(cue->file);
while (isalpha(cue->c))
cue->c = fgetc(cue->file);
return file;
}
cue_t* cue_create(void) {
return malloc(sizeof(cue_t));
}
void cue_init(cue_t* cue) {
cue->files = list_create();
cue->tracks = list_create();
}
int cue_parse(cue_t* cue, const char* path) {
cue->file = fopen(path, "rb");
if (!cue->file)
return CUE_FILE_NOT_FOUND;
const char* s = find_last_slash(path);
cue->c = fgetc(cue->file);
while (isspace(cue->c))
cue->c = fgetc(cue->file);
while (!feof(cue->file)) {
int kw = cue_parse_keyword(cue);
switch (kw) {
case CUE_FILE: {
list_push_back(cue->files, cue_parse_file(cue, path, s));
} break;
case CUE_TRACK: {
cue_track_t* track = cue_parse_track(cue);
cue_file_t* file = list_back(cue->files)->data;
list_push_back(cue->tracks, track);
list_push_back(file->tracks, track);
} break;
case CUE_INDEX: {
cue_parse_index(cue);
} break;
case CUE_REM: case CUE_PREGAP: case CUE_FLAGS: case CUE_POSTGAP: {
// Ignore everything until a newline (handle CRLF and LF)
while ((cue->c != '\n') && (cue->c != '\r'))
cue->c = fgetc(cue->file);
while ((cue->c == '\n') && (cue->c == '\r'))
cue->c = fgetc(cue->file);
} break;
default: {
printf("Unknown keyword: %s (%u)\n", cue_keywords[kw], kw);
return 1;
} break;
}
while (isspace(cue->c))
cue->c = fgetc(cue->file);
}
return 0;
}
size_t get_file_size(FILE* file) {
fseek(file, 0, SEEK_END);
size_t size = ftell(file);
fseek(file, 0, SEEK_SET);
return size;
}
/*
(0 - 0 ) = 150
(0 - 0 ) + 150 + 315000 = 315150
(0 - 0 ) + 315150 + 375 = 315525
(150 - 0 ) + 315525 + 390 = 316065
(195 - 150) + 316065 + 390 = 316500
(155 - 190) + 316500 + 390 =
*/
int prev_pregap = 0;
uint32_t init_tracks(cue_file_t* file, uint32_t* lba) {
node_t* node = list_front(file->tracks);
// 1 track per file case
if (file->tracks->size == 1) {
cue_track_t* data = node->data;
data->pregap = 0;
if ((data->index[0] != -1) && (data->index[1] != -1))
data->pregap = data->index[1];
data->start = *lba + data->pregap;
data->end = data->start + (file->size / 0x930);
*lba = data->end;
return 0;
}
// Multiple tracks per file
while (node) {
cue_track_t* data = node->data;
// If this is the last track
if (!node->next) {
data->pregap = 0;
data->start = data->index[1] + 150;
data->end = file->size / 0x930;
return 0;
}
cue_track_t* next = node->next->data;
data->start = data->index[1] + 150;
data->end = (next->index[1] + 150) - 1;
data->pregap = 0;
node = node->next;
}
return 0;
}
int cue_load(cue_t* cue, int mode) {
node_t* node = list_front(cue->files);
// 00:02:00
uint32_t lba = 2 * 75;
while (node) {
cue_file_t* data = node->data;
FILE* file = fopen(data->name, "rb");
if (!file)
return CUE_TRACK_FILE_NOT_FOUND;
data->buf_mode = mode;
data->size = get_file_size(file);
// printf("Loaded \'%s\': size=%llx, sectors=%llu\n",
// data->name,
// data->size,
// data->size / 0x930
// );
if (data->buf_mode == LD_BUFFERED) {
data->buf = malloc(data->size);
fseek(file, 0, SEEK_SET);
fread(data->buf, 1, data->size, file);
fclose(file);
} else {
data->buf = file;
}
data->start = lba;
init_tracks(data, &lba);
node = node->next;
}
return CUE_OK;
}
void cue_destroy(cue_t* cue) {
node_t* node = list_front(cue->files);
while (node) {
cue_file_t* file = node->data;
if (file->buf_mode == LD_BUFFERED) {
free(file->buf);
} else {
fclose((FILE*)file->buf);
}
list_destroy(file->tracks);
free(file->name);
free(file);
node = node->next;
}
list_destroy(cue->files);
node = list_front(cue->tracks);
while (node) {
free(node->data);
node = node->next;
}
list_destroy(cue->tracks);
free(cue);
}
cue_track_t* get_sector_track(cue_t* cue, uint32_t lba) {
node_t* node = list_front(cue->tracks);
while (node) {
cue_track_t* track = node->data;
if ((lba >= track->start) && (lba < track->end))
return track;
node = node->next;
}
return NULL;
}
cue_track_t* get_sector_track_in_pregap(cue_t* cue, uint32_t lba) {
node_t* node = list_front(cue->tracks);
while (node) {
cue_track_t* track = node->data;
if (!node->next)
return track;
cue_track_t* next = node->next->data;
// Ignore sector number
int curr_start = track->start - (track->start % 75);
int next_start = next->start - (next->start % 75);
if ((lba >= curr_start) && (lba < next_start))
return track;
node = node->next;
}
return NULL;
}
int cue_query(cue_t* cue, uint32_t lba) {
if (lba >= ((cue_track_t*)list_back(cue->tracks)->data)->end)
return TS_FAR;
cue_track_t* track = get_sector_track(cue, lba);
// If the LBA isn't too far but the track wasn't found
// then we are being requested a pregap sector. Clear buffer
// and initialize sync data (not actually needed)
if (!track)
return TS_PREGAP;
return (track->mode == CUE_MODE2_2352) ? TS_DATA : TS_AUDIO;
}
int cue_read(cue_t* cue, uint32_t lba, void* buf) {
if (lba >= ((cue_track_t*)list_back(cue->tracks)->data)->end)
return TS_FAR;
cue_track_t* track = get_sector_track(cue, lba);
// If the LBA isn't too far but the track wasn't found
// then we are being requested a pregap sector. Clear buffer
// and initialize sync data (not actually needed)
if (!track) {
memset(buf, 0, 2352);
memset(buf + 1, 255, 10);
return TS_PREGAP;
}
cue_file_t* file = track->file;
// printf("Reading sector %u at track %u, file=%s (%u), offset=%u (%08x)\n",
// lba,
// track->number,
// track->file->name,
// file->start,
// lba - file->start,
// (lba - file->start) * 2352
// );
if (file->buf_mode == LD_BUFFERED) {
uint8_t* ptr = file->buf + ((lba - file->start) * 2352);
memcpy(buf, ptr, 2352);
} else {
fseek(file->buf, (lba - file->start) * 2352, SEEK_SET);
fread(buf, 1, 2352, file->buf);
}
return (track->mode == CUE_MODE2_2352) ? TS_DATA : TS_AUDIO;
}
int cue_get_track_number(cue_t* cue, uint32_t lba) {
cue_track_t* track = get_sector_track_in_pregap(cue, lba);
return track->number;
}
int cue_get_track_count(cue_t* cue) {
return cue->tracks->size;
}
int cue_get_track_lba(cue_t* cue, int track) {
if (!track)
return ((cue_track_t*)list_back(cue->tracks)->data)->end;
if (track > cue->tracks->size)
return TS_FAR;
cue_track_t* data = list_at(cue->tracks, track - 1)->data;
return data->start;
}
void cue_init_disc(cue_t* cue, psx_disc_t* disc) {
disc->udata = cue;
disc->read_sector = cue_read;
disc->query_sector = cue_query;
disc->get_track_number = cue_get_track_number;
disc->get_track_count = cue_get_track_count;
disc->get_track_lba = cue_get_track_lba;
disc->destroy = cue_destroy;
}