From 079b8bcf1fa30605b15873c5c577b98b54f0e584 Mon Sep 17 00:00:00 2001 From: Antonio SJ Musumeci Date: Mon, 1 Jun 2026 00:20:38 -0500 Subject: [PATCH] cuefile: parse multi-file track metadata --- cuefile.c | 331 ++++++++++++++++++++++++++++++++++-------------------- cuefile.h | 28 ++++- 2 files changed, 237 insertions(+), 122 deletions(-) diff --git a/cuefile.c b/cuefile.c index 7f3cace..6d937eb 100644 --- a/cuefile.c +++ b/cuefile.c @@ -11,164 +11,257 @@ #define STRING_MAX 4096 -static RFILE *cue_get_file_for_image(const char *path) +static +RFILE* +cue_get_file_for_image(const char *path) { - int i; - char cue_path_base[STRING_MAX]; - char cue_path[8192]; - char *exts[] = {".cue", ".CUE"}; - char *last_dot = NULL; + int i; + char cue_path_base[STRING_MAX]; + char cue_path[8192]; + char *exts[] = {".cue", ".CUE"}; + char *last_dot = NULL; - strcpy(cue_path_base, path); + strcpy(cue_path_base, path); - last_dot = strrchr(cue_path_base, '.'); - if (last_dot == NULL) - return NULL; + last_dot = strrchr(cue_path_base, '.'); + if(last_dot == NULL) + return NULL; - /* cut original extension */ - *(last_dot) = '\0'; + /* cut original extension */ + *(last_dot) = '\0'; - for(i=0; i<2; i++) - { + for(i=0; i<2; i++) + { RFILE *cue_file = NULL; strcpy(cue_path, cue_path_base); strcat(cue_path, exts[i]); cue_file = filestream_open(cue_path, RETRO_VFS_FILE_ACCESS_READ, 0); - if (cue_file) - return cue_file; - } + if(cue_file) + return cue_file; + } - return NULL; + return NULL; } -static void str_to_upper(char *s) +static +void +str_to_upper(char *s) { - for ( ; *s; ++s) *s = toupper(*s); + for( ; *s; ++s) + *s = toupper((unsigned char)*s); } -static char *extract_file_name(const char *path, char *line) +static +char* +extract_file_name(const char *path, + char *line) { - char file[STRING_MAX]; - char base_path[STRING_MAX]; - char cd_image[STRING_MAX]; - char *last_separator = NULL; + char file[STRING_MAX]; + char base_path[STRING_MAX]; + char cd_image[STRING_MAX]; + char *last_separator = NULL; #ifdef _WIN32 - char slash = '\\'; + char slash = '\\'; #else - char slash = '/'; + char slash = '/'; #endif - char *file_name_end = NULL; - char *file_name_start = strstr(line, "\""); - if (!file_name_start) - { - if (retro_log_printf_cb) - retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: Missing quotes in : %s\n", line); + char *file_name_end = NULL; + char *file_name_start = strstr(line, "\""); + if(!file_name_start) + { + if(retro_log_printf_cb) + retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: Missing quotes in : %s\n", line); return NULL; - } + } - strcpy(file, ++file_name_start); - file_name_end = strstr(file, "\""); + strcpy(file, ++file_name_start); + file_name_end = strstr(file, "\""); - if (!file_name_end) - { - if (retro_log_printf_cb) - retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: Missing end quote in : %s\n", line); + if(!file_name_end) + { + if(retro_log_printf_cb) + retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: Missing end quote in : %s\n", line); return NULL; - } + } - *file_name_end = '\0'; + *file_name_end = '\0'; - strcpy(base_path, path); + strcpy(base_path, path); - last_separator = strrchr(base_path, slash); - *last_separator = '\0'; + last_separator = strrchr(base_path, slash); + if(last_separator) + { + *last_separator = '\0'; + sprintf(cd_image, "%s%c%s", base_path, slash, file); + } + else + strcpy(cd_image, file); - sprintf(cd_image, "%s%c%s", base_path, slash, file); - - return strdup(cd_image); + return strdup(cd_image); } -cueFile *cue_get(const char *path) +static +uint32_t +cue_msf_to_lba(const char *msf_) { - char line[STRING_MAX]; - int files_found = 0; - cueFile *cue = NULL; - RFILE *cue_file = - cue_is_cue_path(path) ? - filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0) : - cue_get_file_for_image(path); + uint32_t min; + uint32_t sec; + uint32_t frame; - if (!cue_file) - return NULL; + if(sscanf(msf_,"%u:%u:%u",&min,&sec,&frame) != 3) + return 0; - cue = (cueFile *)malloc(sizeof(cueFile)); - cue->cd_format = CUE_MODE_UNKNOWN; - - while ((filestream_gets(cue_file, line, STRING_MAX))) - { - if (strstr(line, "FILE") && files_found == 0) - { - char *cd_image = extract_file_name(path, line); - if (cd_image) - { - files_found++; - cue->cd_image = cd_image; - } - } - - str_to_upper(line); - if (strstr(line, "TRACK 01")) - { - if (strstr(line,"TRACK 01 MODE1/2048")) - cue->cd_format = MODE1_2048; - else if (strstr(line, "TRACK 01 MODE1/2352")) - cue->cd_format = MODE1_2352; - else if (strstr(line, "TRACK 01 MODE2/2352")) - cue->cd_format = MODE2_2352; - else - { - if (retro_log_printf_cb) - retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: Unknown file format in CUE file: %s -> %s", path, line); - } - break; - } - } - filestream_close(cue_file); - - if (retro_log_printf_cb) - { - retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: CD image file in CUE: %s", - cue->cd_image ? cue->cd_image : "Not found"); - } - - if (cue->cd_format != CUE_MODE_UNKNOWN) - return cue; - - free(cue); - return NULL; + return ((min * 60 * 75) + (sec * 75) + frame); } -const char *cue_get_cd_format_name(CD_format cd_format) +cueFile* +cue_get(const char *path) { - switch (cd_format) - { - case MODE1_2048: - return "MODE1/2048"; - case MODE1_2352: - return "MODE1/2352"; - case MODE2_2352: - return "MODE2/2352"; - default: - break; - } + char line[STRING_MAX]; + char line_upper[STRING_MAX]; + int current_file = -1; + int current_track = -1; + cueFile *cue = NULL; + RFILE *cue_file = + cue_is_cue_path(path) ? + filestream_open(path, RETRO_VFS_FILE_ACCESS_READ, 0) : + cue_get_file_for_image(path); - return "UNKNOWN"; + if(!cue_file) + return NULL; + + cue = (cueFile *)calloc(1,sizeof(cueFile)); + cue->cd_format = CUE_MODE_UNKNOWN; + + while ((filestream_gets(cue_file, line, STRING_MAX))) + { + char *track_ptr; + char *index_ptr; + + strcpy(line_upper,line); + str_to_upper(line_upper); + + if(strstr(line_upper, "FILE ")) + { + char *cd_image = extract_file_name(path, line); + if(cd_image) + { + if(cue->num_files < CUE_MAX_FILES) + { + cue->files[cue->num_files].path = cd_image; + current_file = cue->num_files; + if(cue->cd_image == NULL) + cue->cd_image = cd_image; + cue->num_files++; + } + else + free(cd_image); + } + } + + track_ptr = strstr(line_upper, "TRACK "); + if(track_ptr) + { + uint32_t track_num; + char *mode_ptr; + + track_num = (uint32_t)strtoul(track_ptr + 6, NULL, 10); + if(track_num > 0 && track_num <= CUE_MAX_TRACKS && cue->num_tracks < CUE_MAX_TRACKS) + { + current_track = cue->num_tracks; + memset(&cue->tracks[current_track],0,sizeof(cue->tracks[current_track])); + cue->tracks[current_track].track_num = (uint8_t)track_num; + cue->tracks[current_track].file_index = (current_file >= 0) ? + (uint8_t)current_file : 0; + cue->tracks[current_track].format = CUE_MODE_UNKNOWN; + + mode_ptr = track_ptr + 8; + while(*mode_ptr && isspace((unsigned char)*mode_ptr)) + mode_ptr++; + + if(strstr(mode_ptr,"MODE1/2048")) + cue->tracks[current_track].format = MODE1_2048; + else if(strstr(mode_ptr,"MODE1/2352")) + cue->tracks[current_track].format = MODE1_2352; + else if(strstr(mode_ptr,"MODE2/2352")) + cue->tracks[current_track].format = MODE2_2352; + else if(strstr(mode_ptr,"AUDIO")) + cue->tracks[current_track].format = CUE_MODE_AUDIO; + } + } + + index_ptr = strstr(line_upper, "INDEX 01"); + if(index_ptr && current_track == cue->num_tracks && cue->num_tracks < CUE_MAX_TRACKS) + { + char *msf_ptr = index_ptr + 8; + while(*msf_ptr && !isdigit((unsigned char)*msf_ptr)) + msf_ptr++; + cue->tracks[cue->num_tracks].index_lba = cue_msf_to_lba(msf_ptr); + cue->num_tracks++; + current_track = -1; + } + } + filestream_close(cue_file); + + if(cue->num_tracks > 0 && cue->cd_format == CUE_MODE_UNKNOWN) + cue->cd_format = cue->tracks[0].format; + + if(retro_log_printf_cb) + { + retro_log_printf_cb(RETRO_LOG_INFO, "[Opera]: CD image files in CUE: %d first=%s\n", + cue->num_files, + cue->cd_image ? cue->cd_image : "Not found"); + } + + if((cue->cd_format != CUE_MODE_UNKNOWN || cue->num_tracks > 0) && + cue->num_files > 0) + return cue; + + cue_free(cue); + return NULL; } -int cue_is_cue_path(const char *path) +void +cue_free(cueFile *cue) { - char *dot = strrchr(path, '.'); - return (dot && (!strcmp(dot, ".cue") || !strcmp(dot, ".CUE"))); + int i; + + if(!cue) + return; + + for(i = 0; i < cue->num_files; i++) + free(cue->files[i].path); + + free(cue); +} + +const +char* +cue_get_cd_format_name(CD_format cd_format) +{ + switch (cd_format) + { + case MODE1_2048: + return "MODE1/2048"; + case MODE1_2352: + return "MODE1/2352"; + case MODE2_2352: + return "MODE2/2352"; + case CUE_MODE_AUDIO: + return "AUDIO"; + default: + break; + } + + return "UNKNOWN"; +} + +int +cue_is_cue_path(const char *path) +{ + const char *dot = strrchr(path, '.'); + return (dot && (!strcmp(dot, ".cue") || + !strcmp(dot, ".CUE"))); } diff --git a/cuefile.h b/cuefile.h index 02a6df0..1bebe33 100644 --- a/cuefile.h +++ b/cuefile.h @@ -1,6 +1,8 @@ #ifndef CUEFILE_H__ #define CUEFILE_H__ +#include + #define SECTOR_SIZE_2048 2048 #define SECTOR_SIZE_2352 2352 @@ -8,14 +10,34 @@ #define SECTOR_OFFSET_MODE1_2352 16 #define SECTOR_OFFSET_MODE2_2352 24 -typedef enum {MODE1_2048, MODE1_2352, MODE2_2352, CUE_MODE_UNKNOWN} CD_format; +#define CUE_MAX_TRACKS 100 +#define CUE_MAX_FILES CUE_MAX_TRACKS + +typedef enum {MODE1_2048, MODE1_2352, MODE2_2352, CUE_MODE_AUDIO, CUE_MODE_UNKNOWN} CD_format; typedef struct { - CD_format cd_format; - char * cd_image; + uint8_t track_num; + uint8_t file_index; + CD_format format; + uint32_t index_lba; + uint32_t frames; +} cue_track_t; + +typedef struct { + char *path; +} cue_file_t; + +typedef struct { + CD_format cd_format; + char * cd_image; + int num_files; + cue_file_t files[CUE_MAX_FILES]; + int num_tracks; + cue_track_t tracks[CUE_MAX_TRACKS]; } cueFile; cueFile *cue_get(const char *path); +void cue_free(cueFile *cue); const char *cue_get_cd_format_name(CD_format cd_format); int cue_is_cue_path(const char *path);