mirror of
https://github.com/grumpycoders/pcsx-redux.git
synced 2025-04-02 10:41:54 -04:00
738 lines
30 KiB
C
738 lines
30 KiB
C
/*
|
|
|
|
MIT License
|
|
|
|
Copyright (c) 2022 Nicolas "Pixel" Noble
|
|
|
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
of this software and associated documentation files (the "Software"), to deal
|
|
in the Software without restriction, including without limitation the rights
|
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
copies of the Software, and to permit persons to whom the Software is
|
|
furnished to do so, subject to the following conditions:
|
|
|
|
The above copyright notice and this permission notice shall be included in all
|
|
copies or substantial portions of the Software.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
SOFTWARE.
|
|
|
|
*/
|
|
|
|
#include "cueparser/cueparser.h"
|
|
|
|
#include <assert.h>
|
|
#include <ctype.h>
|
|
#include <stdint.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
|
|
#include "cueparser/disc.h"
|
|
#include "cueparser/fileabstract.h"
|
|
#include "cueparser/scheduler.h"
|
|
|
|
#pragma GCC diagnostic ignored "-Wswitch"
|
|
|
|
enum Keyword {
|
|
KW_EMPTY = 0x00001505,
|
|
KW_4CH = 0x0b86667a,
|
|
KW_AIFF = 0x7c7ea8ad,
|
|
KW_AUDIO = 0x0c561a33,
|
|
KW_BINARY = 0x9da252ca,
|
|
KW_CATALOG = 0x2316d516,
|
|
KW_CDG = 0x0b87cf65,
|
|
KW_CDI_2336 = 0x2f162780,
|
|
KW_CDI_2352 = 0x2f1626c2,
|
|
KW_CDTEXTFILE = 0xbbd08639,
|
|
KW_DCP = 0x0b87bad2,
|
|
KW_FILE = 0x7c7e1383,
|
|
KW_FLAC = 0x7c7e292d,
|
|
KW_FLAGS = 0x0c434e1a,
|
|
KW_INDEX = 0x0cda305b,
|
|
KW_ISRC = 0x7c8344ae,
|
|
KW_MODE1_2048 = 0x10a03916,
|
|
KW_MODE1_2352 = 0x10a02cbe,
|
|
KW_MODE2_2336 = 0x0e924c9f,
|
|
KW_MODE2_2352 = 0x0e924d5d,
|
|
KW_MOTOROLA = 0x7ba98cec,
|
|
KW_MP3 = 0x0b87c98b,
|
|
KW_OGG = 0x0b87e14a,
|
|
KW_OPUS = 0x7c84409c,
|
|
KW_PERFORMER = 0xcc7e14e3,
|
|
KW_POSTGAP = 0xb61fc6ab,
|
|
KW_PRE = 0x0b880de2,
|
|
KW_PREGAP = 0xc625ff94,
|
|
KW_REM = 0x0b88069f,
|
|
KW_SCMS = 0x7c8a8e8b,
|
|
KW_SONGWRITER = 0xb05cc69f,
|
|
KW_TITLE = 0x0d83f885,
|
|
KW_TRACK = 0x0d7ac5ca,
|
|
KW_WAVE = 0x7c885360,
|
|
};
|
|
|
|
static void new_keyword(struct CueParser* parser) { parser->keyword = 5381; }
|
|
|
|
static void keyword_add_char(struct CueParser* parser, int c) {
|
|
uint32_t hash = parser->keyword;
|
|
hash = ((hash << 5) + hash) ^ c;
|
|
parser->keyword = hash;
|
|
}
|
|
|
|
struct end_Closure {
|
|
struct CueScheduler* scheduler;
|
|
void (*destroy)(struct CueClosure*);
|
|
void (*call)(struct CueClosure*);
|
|
struct CueClosure* next;
|
|
struct CueParser* parser;
|
|
const char* error;
|
|
};
|
|
|
|
static void closure_generic_free(struct CueClosure* closure) { free(closure); }
|
|
|
|
static void end_closure_call(struct CueClosure* closure_) {
|
|
struct end_Closure* closure = (struct end_Closure*)closure_;
|
|
closure->parser->cb(closure->parser, closure->scheduler, closure->error);
|
|
}
|
|
|
|
static void reset_word(char* word) { *(uint8_t*)&word[255] = 0; }
|
|
|
|
static int append_to_word(char* word, int c) {
|
|
uint8_t* b = (uint8_t*)word;
|
|
int len = b[255];
|
|
if (len == 255) return 0;
|
|
word[len++] = c;
|
|
b[255] = len;
|
|
return 1;
|
|
}
|
|
|
|
static int word_len(char* word) {
|
|
uint8_t* b = (uint8_t*)word;
|
|
return b[255];
|
|
}
|
|
|
|
static void end_word(char* word) {
|
|
int len = word_len(word);
|
|
word[len] = 0;
|
|
reset_word(word);
|
|
}
|
|
|
|
void CueParser_construct(struct CueParser* parser, struct CueDisc* disc) {
|
|
parser->disc = disc;
|
|
parser->cursor = 0;
|
|
reset_word(parser->word);
|
|
parser->amount = 0;
|
|
parser->state = CUE_PARSER_START;
|
|
parser->inQuotes = 0;
|
|
parser->afterQuotes = 0;
|
|
parser->inRem = 0;
|
|
parser->gotSpace = 1;
|
|
parser->currentFileSize = 0;
|
|
parser->currentFile = NULL;
|
|
parser->currentTrack = 0;
|
|
parser->currentSectorNumber = 0;
|
|
parser->isTrackANewFile = 0;
|
|
disc->catalog[0] = 0;
|
|
disc->isrc[0] = 0;
|
|
disc->trackCount = 0;
|
|
for (unsigned i = 0; i < MAXTRACK; i++) {
|
|
disc->tracks[i].file = NULL;
|
|
}
|
|
new_keyword(parser);
|
|
}
|
|
|
|
static void close_cb(struct CueFile* file, struct CueScheduler* scheduler) {
|
|
struct CueParser* parser = file->user;
|
|
parser->cb(parser, scheduler, NULL);
|
|
}
|
|
|
|
void CueParser_close(struct CueParser* parser, struct CueScheduler* scheduler,
|
|
void (*cb)(struct CueParser*, struct CueScheduler*, const char*)) {
|
|
if (parser->currentFile) {
|
|
if (--parser->currentFile->references) {
|
|
parser->cb = cb;
|
|
parser->currentFile->user = parser;
|
|
parser->currentFile->close(parser->currentFile, scheduler, close_cb);
|
|
}
|
|
}
|
|
}
|
|
|
|
void CueParser_destroy(struct CueParser* parser) {}
|
|
|
|
static int needs_argument(struct CueParser* parser) {
|
|
switch (parser->state) {
|
|
case CUE_PARSER_CATALOG:
|
|
case CUE_PARSER_CDTEXTFILE:
|
|
case CUE_PARSER_FILE_FILENAME:
|
|
case CUE_PARSER_INDEX_NUMBER:
|
|
case CUE_PARSER_INDEX_TIMECODE:
|
|
case CUE_PARSER_ISRC:
|
|
case CUE_PARSER_PERFORMER:
|
|
case CUE_PARSER_POSTGAP:
|
|
case CUE_PARSER_PREGAP:
|
|
case CUE_PARSER_SONGWRITER:
|
|
case CUE_PARSER_TITLE:
|
|
case CUE_PARSER_TRACK_NUMBER:
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static void schedule_read(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler);
|
|
static void size_cb(struct CueFile* file, struct CueScheduler* scheduler, uint64_t size);
|
|
|
|
static int32_t timecodeToSectorNumber(char* timecode) {
|
|
char* endptr;
|
|
int min = strtol(timecode, &endptr, 10);
|
|
if (*endptr != ':' || (min < 0) || (min >= 100)) {
|
|
return -1;
|
|
}
|
|
int sec = strtol(endptr + 1, &endptr, 10);
|
|
if (*endptr != ':' || (sec < 0) || (sec >= 60)) {
|
|
return -1;
|
|
}
|
|
int fra = strtol(endptr + 1, &endptr, 10);
|
|
if (*endptr || (fra < 0) || (fra >= 75)) {
|
|
return -1;
|
|
}
|
|
return fra + sec * 75 + min * 60 * 75;
|
|
}
|
|
|
|
void end_parse(struct CueParser* parser, struct CueScheduler* scheduler, const char* error) {
|
|
struct end_Closure* closure = malloc(sizeof(struct end_Closure));
|
|
assert(closure);
|
|
closure->destroy = closure_generic_free;
|
|
closure->call = end_closure_call;
|
|
closure->parser = parser;
|
|
closure->error = error;
|
|
Scheduler_schedule(scheduler, (struct CueClosure*)closure);
|
|
}
|
|
|
|
static void parse(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) {
|
|
while (parser->amount--) {
|
|
int c = *parser->start++;
|
|
int isEOW = isspace(c);
|
|
int isEOL = (c == '\r') || (c == '\n');
|
|
int isSpace = isEOW && !isEOL;
|
|
if (!isEOW) keyword_add_char(parser, c);
|
|
if (parser->inQuotes) {
|
|
if (c == '"') {
|
|
parser->inQuotes = 0;
|
|
parser->afterQuotes = 1;
|
|
} else {
|
|
if (!append_to_word(parser->word, c)) {
|
|
end_parse(parser, scheduler, "cuesheet argument too long");
|
|
return;
|
|
}
|
|
}
|
|
continue;
|
|
}
|
|
if (parser->inRem) {
|
|
if (isEOL) {
|
|
parser->inRem = 0;
|
|
new_keyword(parser);
|
|
}
|
|
continue;
|
|
}
|
|
if (parser->afterQuotes) {
|
|
if (!isEOW) {
|
|
end_parse(parser, scheduler, "cuesheet quote imbalance (got characters after quotes)");
|
|
return;
|
|
}
|
|
parser->afterQuotes = 0;
|
|
}
|
|
if (!isEOW) {
|
|
if (needs_argument(parser)) {
|
|
if (c == '"') {
|
|
if (parser->gotSpace) {
|
|
parser->inQuotes = 1;
|
|
} else {
|
|
end_parse(parser, scheduler,
|
|
"cuesheet quote imbalance (got a quote in the middle of an argument)");
|
|
return;
|
|
}
|
|
} else if (!append_to_word(parser->word, c)) {
|
|
end_parse(parser, scheduler, "cuesheet argument too long");
|
|
return;
|
|
}
|
|
}
|
|
parser->gotSpace = 0;
|
|
continue;
|
|
}
|
|
parser->gotSpace = 1;
|
|
enum Keyword keyword = parser->keyword;
|
|
if ((keyword == KW_EMPTY) && isSpace) continue;
|
|
int len = word_len(parser->word);
|
|
if (needs_argument(parser)) end_word(parser->word);
|
|
new_keyword(parser);
|
|
switch (parser->state) {
|
|
case CUE_PARSER_START:
|
|
switch (keyword) {
|
|
case KW_EMPTY:
|
|
break;
|
|
case KW_REM:
|
|
parser->inRem = 1;
|
|
break;
|
|
case KW_CATALOG:
|
|
if (parser->disc->catalog[0]) {
|
|
end_parse(parser, scheduler, "cuesheet has too many CATALOG arguments");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_CATALOG;
|
|
break;
|
|
case KW_CDTEXTFILE:
|
|
parser->state = CUE_PARSER_CDTEXTFILE;
|
|
break;
|
|
case KW_FILE:
|
|
parser->state = CUE_PARSER_FILE_FILENAME;
|
|
break;
|
|
case KW_FLAGS:
|
|
parser->state = CUE_PARSER_FLAGS;
|
|
break;
|
|
case KW_INDEX:
|
|
parser->state = CUE_PARSER_INDEX_NUMBER;
|
|
break;
|
|
case KW_ISRC:
|
|
if (parser->disc->isrc[0]) {
|
|
end_parse(parser, scheduler, "cuesheet has too many ISRC arguments");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_ISRC;
|
|
break;
|
|
case KW_PERFORMER:
|
|
parser->state = CUE_PARSER_PERFORMER;
|
|
break;
|
|
case KW_POSTGAP:
|
|
parser->state = CUE_PARSER_POSTGAP;
|
|
break;
|
|
case KW_PREGAP:
|
|
parser->state = CUE_PARSER_PREGAP;
|
|
break;
|
|
case KW_SONGWRITER:
|
|
parser->state = CUE_PARSER_SONGWRITER;
|
|
break;
|
|
case KW_TITLE:
|
|
parser->state = CUE_PARSER_SONGWRITER;
|
|
break;
|
|
case KW_TRACK:
|
|
parser->state = CUE_PARSER_TRACK_NUMBER;
|
|
break;
|
|
default:
|
|
end_parse(parser, scheduler, "unknown keyword in cuesheet");
|
|
return;
|
|
}
|
|
break;
|
|
case CUE_PARSER_CATALOG:
|
|
if (len != 13) {
|
|
end_parse(parser, scheduler, "cuesheet CATALOG argument isn't 13 characters");
|
|
return;
|
|
}
|
|
memcpy(parser->disc->catalog, parser->word, 14);
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
case CUE_PARSER_CDTEXTFILE:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet CDTEXTFILE missing its filename argument");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
end_parse(parser, scheduler, "cuesheet CDTEXTFILE not supported at the moment");
|
|
return;
|
|
break;
|
|
case CUE_PARSER_FILE_FILENAME:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet FILE missing its filename argument");
|
|
return;
|
|
} else {
|
|
struct CueFile* binaryFile = malloc(sizeof(struct CueFile));
|
|
assert(binaryFile);
|
|
binaryFile->user = file;
|
|
if (parser->isTrackANewFile) {
|
|
end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK");
|
|
return;
|
|
}
|
|
if (parser->currentFile) {
|
|
if (parser->currentFile->references == 1) {
|
|
end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK");
|
|
return;
|
|
}
|
|
parser->currentFile->references--;
|
|
parser->currentFile = NULL;
|
|
}
|
|
if (!parser->open(binaryFile, scheduler, parser->word)) {
|
|
binaryFile->destroy(binaryFile);
|
|
free(binaryFile);
|
|
end_parse(parser, scheduler, "cuesheet references a file that can't be found");
|
|
return;
|
|
}
|
|
parser->isTrackANewFile = 1;
|
|
parser->currentFile = binaryFile;
|
|
}
|
|
parser->state = CUE_PARSER_FILE_FILETYPE;
|
|
break;
|
|
case CUE_PARSER_FILE_FILETYPE:
|
|
parser->state = CUE_PARSER_START;
|
|
switch (keyword) {
|
|
case KW_EMPTY:
|
|
end_parse(parser, scheduler, "cuesheet FILE missing its filetype argument");
|
|
return;
|
|
break;
|
|
case KW_BINARY:
|
|
parser->currentFileType = CUE_FILE_TYPE_BINARY;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 0, size_cb);
|
|
return;
|
|
break;
|
|
case KW_MOTOROLA:
|
|
parser->currentFileType = CUE_FILE_TYPE_MOTOROLA;
|
|
end_parse(parser, scheduler, "cuesheet FILETYPE MOTOROLA not supported at the moment");
|
|
return;
|
|
break;
|
|
case KW_AIFF:
|
|
parser->currentFileType = CUE_FILE_TYPE_AIFF;
|
|
end_parse(parser, scheduler, "cuesheet FILETYPE AIFF not supported at the moment");
|
|
return;
|
|
break;
|
|
case KW_WAVE:
|
|
parser->currentFileType = CUE_FILE_TYPE_WAVE;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb);
|
|
return;
|
|
break;
|
|
case KW_MP3:
|
|
parser->currentFileType = CUE_FILE_TYPE_MP3;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb);
|
|
return;
|
|
break;
|
|
case KW_OGG:
|
|
parser->currentFileType = CUE_FILE_TYPE_OGG;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb);
|
|
return;
|
|
break;
|
|
case KW_OPUS:
|
|
parser->currentFileType = CUE_FILE_TYPE_OPUS;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb);
|
|
return;
|
|
break;
|
|
case KW_FLAC:
|
|
parser->currentFileType = CUE_FILE_TYPE_FLAC;
|
|
parser->currentFile->size(parser->currentFile, scheduler, 1, size_cb);
|
|
return;
|
|
break;
|
|
default:
|
|
end_parse(parser, scheduler, "cuesheet unknown FILE filetype");
|
|
return;
|
|
break;
|
|
}
|
|
break;
|
|
case CUE_PARSER_FLAGS: {
|
|
struct CueTrack* track = &parser->disc->tracks[parser->currentTrack];
|
|
switch (keyword) {
|
|
case KW_EMPTY:
|
|
assert(isEOL);
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
case KW_DCP:
|
|
track->digitalCopyPermitted = 1;
|
|
break;
|
|
case KW_4CH:
|
|
track->fourChannelAudio = 1;
|
|
break;
|
|
case KW_PRE:
|
|
track->preEmphasis = 1;
|
|
break;
|
|
case KW_SCMS:
|
|
track->serialCopyManagementSystem = 1;
|
|
break;
|
|
default:
|
|
end_parse(parser, scheduler, "cuesheet FLAGS argument unknown");
|
|
return;
|
|
}
|
|
} break;
|
|
case CUE_PARSER_INDEX_NUMBER: {
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet INDEX missing index number argument");
|
|
return;
|
|
}
|
|
char* endptr;
|
|
int indexNum = strtol(parser->word, &endptr, 10);
|
|
if (*endptr || (indexNum < 0) || (indexNum >= MAXINDEX)) {
|
|
end_parse(parser, scheduler, "cuesheet INDEX number invalid");
|
|
return;
|
|
}
|
|
struct CueTrack* track = &parser->disc->tracks[parser->currentTrack];
|
|
if ((track->indexCount == -1) & (indexNum == 1)) {
|
|
parser->implicitIndex = 1;
|
|
} else if (track->indexCount != (indexNum - 1)) {
|
|
end_parse(parser, scheduler, "cuesheet INDEX not consecutive");
|
|
return;
|
|
}
|
|
track->indexCount = indexNum;
|
|
track->compressed = parser->currentFileType != CUE_FILE_TYPE_BINARY;
|
|
parser->state = CUE_PARSER_INDEX_TIMECODE;
|
|
} break;
|
|
case CUE_PARSER_INDEX_TIMECODE: {
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet INDEX missing timecode argument");
|
|
return;
|
|
}
|
|
int32_t sectorNumber = timecodeToSectorNumber(parser->word);
|
|
if (sectorNumber < 0) {
|
|
end_parse(parser, scheduler, "cuesheet INDEX timecode invalid");
|
|
return;
|
|
}
|
|
struct CueTrack* track = &parser->disc->tracks[parser->currentTrack];
|
|
track->indices[track->indexCount] = parser->currentSectorNumber + sectorNumber;
|
|
if (parser->implicitIndex) {
|
|
if (parser->currentTrack == 1) {
|
|
track->indices[0] = 0;
|
|
} else {
|
|
track->indices[0] = track->indices[1];
|
|
track->indices[1] += parser->currentPregap;
|
|
track->fileOffset += parser->currentPregap;
|
|
parser->currentSectorNumber += parser->currentPregap;
|
|
}
|
|
parser->implicitIndex = 0;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
} break;
|
|
case CUE_PARSER_ISRC:
|
|
if (len != 12) {
|
|
end_parse(parser, scheduler, "cuesheet ISRC doesn't have 12 characters");
|
|
return;
|
|
}
|
|
memcpy(parser->disc->isrc, parser->word, 13);
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
case CUE_PARSER_PERFORMER:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet PERFORMER missing its argument");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
end_parse(parser, scheduler, "cuesheet PERFORMER argument not supported at the moment");
|
|
return;
|
|
break;
|
|
case CUE_PARSER_POSTGAP:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet POSTGAP missing its argument");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
end_parse(parser, scheduler, "cuesheet POSTGAP argument not supported at the moment");
|
|
return;
|
|
break;
|
|
case CUE_PARSER_PREGAP: {
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet PREGAP missing its argument");
|
|
return;
|
|
}
|
|
if (parser->currentTrack == 0) {
|
|
end_parse(parser, scheduler, "cuesheet PREGAP before any TRACK");
|
|
return;
|
|
}
|
|
struct CueTrack* track = &parser->disc->tracks[parser->currentTrack];
|
|
if (track->indexCount != -1) {
|
|
end_parse(parser, scheduler, "cuesheet PREGAP after an INDEX");
|
|
return;
|
|
}
|
|
int32_t pregapLength = timecodeToSectorNumber(parser->word);
|
|
if (pregapLength < 0) {
|
|
end_parse(parser, scheduler, "cuesheet PREGAP length invalid");
|
|
return;
|
|
}
|
|
parser->currentPregap = pregapLength;
|
|
parser->state = CUE_PARSER_START;
|
|
} break;
|
|
case CUE_PARSER_SONGWRITER:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet SONGWRITER missing its argument");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
end_parse(parser, scheduler, "cuesheet SONGWRITER argument not supported at the moment");
|
|
return;
|
|
break;
|
|
case CUE_PARSER_TITLE:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet TITLE missing its argument");
|
|
return;
|
|
}
|
|
parser->state = CUE_PARSER_START;
|
|
end_parse(parser, scheduler, "cuesheet TITLE argument not supported at the moment");
|
|
return;
|
|
break;
|
|
case CUE_PARSER_TRACK_NUMBER:
|
|
if (keyword == KW_EMPTY) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK missing its track number argument");
|
|
return;
|
|
} else {
|
|
parser->state = CUE_PARSER_TRACK_DATATYPE;
|
|
char* endptr;
|
|
int trackNum = strtol(parser->word, &endptr, 10);
|
|
if (*endptr || (trackNum < 1) || (trackNum >= MAXTRACK)) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK number invalid");
|
|
return;
|
|
}
|
|
if (parser->currentTrack != (trackNum - 1)) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK not consecutive");
|
|
return;
|
|
}
|
|
parser->currentTrack = trackNum;
|
|
struct CueTrack* track = &parser->disc->tracks[trackNum];
|
|
if (track->file) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK already exists");
|
|
return;
|
|
}
|
|
if (!parser->currentFile) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK without a FILE first");
|
|
return;
|
|
}
|
|
track->file = parser->currentFile;
|
|
track->file->references++;
|
|
track->fileOffset = 0;
|
|
track->indexCount = -1;
|
|
track->postgap = 0;
|
|
track->size = 0;
|
|
track->trackType = TRACK_TYPE_UNKNOWN;
|
|
track->compressed = 0;
|
|
track->digitalCopyPermitted = 0;
|
|
track->fourChannelAudio = 0;
|
|
track->preEmphasis = 0;
|
|
track->serialCopyManagementSystem = 0;
|
|
parser->currentPregap = 0;
|
|
if (parser->isTrackANewFile) {
|
|
parser->currentSectorNumber += (parser->previousFileSize + 2351) / 2352;
|
|
parser->isTrackANewFile = 0;
|
|
track->fileOffset = parser->currentSectorNumber;
|
|
} else {
|
|
track->fileOffset = parser->disc->tracks[parser->currentTrack - 1].fileOffset;
|
|
}
|
|
parser->disc->trackCount = trackNum;
|
|
}
|
|
break;
|
|
case CUE_PARSER_TRACK_DATATYPE:
|
|
switch (keyword) {
|
|
case KW_AUDIO:
|
|
parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_AUDIO;
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
case KW_CDG:
|
|
end_parse(parser, scheduler, "cuesheet TRACK type CDG not supported at the moment");
|
|
parser->state = CUE_PARSER_START;
|
|
return;
|
|
break;
|
|
case KW_CDI_2336:
|
|
end_parse(parser, scheduler, "cuesheet TRACK type CDI_2336 not supported at the moment");
|
|
parser->state = CUE_PARSER_START;
|
|
return;
|
|
break;
|
|
case KW_CDI_2352:
|
|
end_parse(parser, scheduler, "cuesheet TRACK type CDI_2352 not supported at the moment");
|
|
parser->state = CUE_PARSER_START;
|
|
return;
|
|
break;
|
|
case KW_MODE1_2048:
|
|
end_parse(parser, scheduler, "cuesheet TRACK type MODE1_2048 not supported at the moment");
|
|
parser->state = CUE_PARSER_START;
|
|
return;
|
|
break;
|
|
case KW_MODE1_2352:
|
|
parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_DATA;
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
case KW_MODE2_2336:
|
|
end_parse(parser, scheduler, "cuesheet TRACK type MODE2_2336 not supported at the moment");
|
|
parser->state = CUE_PARSER_START;
|
|
return;
|
|
break;
|
|
case KW_MODE2_2352:
|
|
parser->disc->tracks[parser->currentTrack].trackType = TRACK_TYPE_DATA;
|
|
parser->state = CUE_PARSER_START;
|
|
break;
|
|
default:
|
|
end_parse(parser, scheduler, "cuesheet TRACK has unknown or missing datatype");
|
|
return;
|
|
}
|
|
break;
|
|
default:
|
|
end_parse(parser, scheduler, "cuesheet parser internal error");
|
|
return;
|
|
}
|
|
}
|
|
parser->amount = 0;
|
|
schedule_read(parser, file, scheduler);
|
|
}
|
|
|
|
static void parse_eof(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) {
|
|
if (parser->state != CUE_PARSER_START) {
|
|
parser->amount = 1;
|
|
parser->start = "\n";
|
|
parse(parser, file, scheduler);
|
|
return;
|
|
}
|
|
if (parser->currentFile) {
|
|
if (parser->currentFile->references == 1) {
|
|
end_parse(parser, scheduler, "cuesheet has too many FILE without TRACK");
|
|
return;
|
|
}
|
|
parser->currentFile->references--;
|
|
}
|
|
parser->currentFile = NULL;
|
|
if (parser->disc->trackCount == 0) {
|
|
end_parse(parser, scheduler, "cuesheet has no track");
|
|
return;
|
|
}
|
|
for (unsigned i = 1; i < parser->disc->trackCount; i++) {
|
|
struct CueTrack* track = &parser->disc->tracks[i];
|
|
if (track->indexCount < 1) {
|
|
end_parse(parser, scheduler, "cuesheet TRACK doesn't have enough indices");
|
|
return;
|
|
}
|
|
}
|
|
for (unsigned i = 2; i <= parser->disc->trackCount; i++) {
|
|
struct CueTrack* prevTrack = &parser->disc->tracks[i - 1];
|
|
struct CueTrack* track = &parser->disc->tracks[i];
|
|
prevTrack->size = track->indices[0] - prevTrack->indices[0];
|
|
}
|
|
parser->currentSectorNumber += (parser->currentFileSize + 2351) / 2352;
|
|
struct CueTrack* track = &parser->disc->tracks[parser->disc->trackCount];
|
|
track->size = parser->currentSectorNumber - track->indices[0];
|
|
end_parse(parser, scheduler, NULL);
|
|
}
|
|
|
|
static void read_bytes(struct CueFile* file, struct CueScheduler* scheduler, int error, uint32_t amount,
|
|
uint8_t* buffer) {
|
|
struct CueParser* parser = file->user;
|
|
if (amount == 0) {
|
|
parse_eof(parser, file, scheduler);
|
|
} else {
|
|
parser->amount = amount;
|
|
parser->cursor += amount;
|
|
parse(parser, file, scheduler);
|
|
}
|
|
}
|
|
static void size_cb(struct CueFile* binaryFile, struct CueScheduler* scheduler, uint64_t size) {
|
|
struct CueFile* file = binaryFile->user;
|
|
struct CueParser* parser = file->user;
|
|
parser->previousFileSize = parser->currentFileSize;
|
|
parser->currentFileSize = size;
|
|
parse(parser, file, scheduler);
|
|
}
|
|
|
|
static void schedule_read(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler) {
|
|
assert(parser->amount == 0);
|
|
parser->start = parser->buffer;
|
|
file->read(file, scheduler, sizeof(parser->buffer), parser->cursor, (uint8_t*)parser->buffer, read_bytes);
|
|
}
|
|
|
|
void CueParser_parse(struct CueParser* parser, struct CueFile* file, struct CueScheduler* scheduler,
|
|
struct CueFile* (*fileopen)(struct CueFile*, struct CueScheduler*, const char*),
|
|
void (*cb)(struct CueParser*, struct CueScheduler*, const char*)) {
|
|
file->user = parser;
|
|
parser->cb = cb;
|
|
parser->open = fileopen;
|
|
schedule_read(parser, file, scheduler);
|
|
}
|