2b92bee80cb8d8da3e20f00221ffd36a05daf1d6
[picodrive.git] / pico / cd / cue.c
1 /*
2  * cuefile handling
3  * (C) notaz, 2008
4  *
5  * This work is licensed under the terms of MAME license.
6  * See COPYING file in the top-level directory.
7  */
8 #include <stdio.h>
9 #include <stdlib.h>
10 #include <string.h>
11 #include "cue.h"
12
13 #include "../pico_int.h"
14 // #define elprintf(w,f,...) printf(f "\n",##__VA_ARGS__);
15
16 #ifdef _MSC_VER
17 #define snprintf _snprintf
18 #endif
19 #ifdef __EPOC32__
20 #define snprintf(b,s,...) sprintf(b,##__VA_ARGS__)
21 #endif
22
23 static char *mystrip(char *str)
24 {
25         int i, len;
26
27         len = strlen(str);
28         for (i = 0; i < len; i++)
29                 if (str[i] != ' ') break;
30         if (i > 0) memmove(str, str + i, len - i + 1);
31
32         len = strlen(str);
33         for (i = len - 1; i >= 0; i--)
34                 if (str[i] != ' ' && str[i] != '\r' && str[i] != '\n') break;
35         str[i+1] = 0;
36
37         return str;
38 }
39
40 static int get_token(const char *buff, char *dest, int len)
41 {
42         const char *p = buff;
43         char sep = ' ';
44         int d = 0, skip = 0;
45
46         while (*p && *p == ' ') {
47                 skip++;
48                 p++;
49         }
50
51         if (*p == '\"') {
52                 sep = '\"';
53                 p++;
54         }
55         while (*p && *p != sep && d < len-1)
56                 dest[d++] = *p++;
57         dest[d] = 0;
58
59         if (sep == '\"' && *p != sep)
60                 elprintf(EL_STATUS, "cue: bad token: \"%s\"", buff);
61
62         return d + skip;
63 }
64
65 static char *get_ext(char *fname)
66 {
67         int len = strlen(fname);
68         return (len >= 3) ? (fname + len - 3) : fname;
69 }
70
71
72 #define BEGINS(buff,str) (strncmp(buff,str,sizeof(str)-1) == 0)
73
74 /* note: tracks[0] is not used */
75 cue_data_t *cue_parse(const char *fname)
76 {
77         char buff[256], current_file[256], buff2[32], *current_filep;
78         FILE *f, *tmpf;
79         int ret, count = 0, count_alloc = 2, pending_pregap = 0;
80         cue_data_t *data;
81         void *tmp;
82
83         f = fopen(fname, "r");
84         if (f == NULL) return NULL;
85
86         snprintf(current_file, sizeof(current_file), "%s", fname);
87         for (current_filep = current_file + strlen(current_file); current_filep > current_file; current_filep--)
88                 if (current_filep[-1] == '/' || current_filep[-1] == '\\') break;
89
90         data = calloc(1, sizeof(*data) + count_alloc * sizeof(cue_track));
91         if (data == NULL) {
92                 fclose(f);
93                 return NULL;
94         }
95
96         while (!feof(f))
97         {
98                 tmp = fgets(buff, sizeof(buff), f);
99                 if (tmp == NULL) break;
100
101                 mystrip(buff);
102                 if (buff[0] == 0) continue;
103                 if      (BEGINS(buff, "TITLE ") || BEGINS(buff, "PERFORMER ") || BEGINS(buff, "SONGWRITER "))
104                         continue;       /* who would put those here? Ignore! */
105                 else if (BEGINS(buff, "FILE "))
106                 {
107                         get_token(buff+5, current_filep, sizeof(current_file) - (current_filep - current_file));
108                 }
109                 else if (BEGINS(buff, "TRACK "))
110                 {
111                         count++;
112                         if (count >= count_alloc) {
113                                 count_alloc *= 2;
114                                 tmp = realloc(data, sizeof(*data) + count_alloc * sizeof(cue_track));
115                                 if (tmp == NULL) { count--; break; }
116                                 data = tmp;
117                         }
118                         memset(&data->tracks[count], 0, sizeof(data->tracks[0]));
119                         if (count == 1 || strcmp(data->tracks[1].fname, current_file) != 0)
120                         {
121                                 data->tracks[count].fname = strdup(current_file);
122                                 if (data->tracks[count].fname == NULL) break;
123
124                                 tmpf = fopen(current_file, "rb");
125                                 if (tmpf == NULL) {
126                                         elprintf(EL_STATUS, "cue: bad/missing file: \"%s\"", current_file);
127                                         count--; break;
128                                 }
129                                 fclose(tmpf);
130                         }
131                         data->tracks[count].pregap = pending_pregap;
132                         pending_pregap = 0;
133                         // track number
134                         ret = get_token(buff+6, buff2, sizeof(buff2));
135                         if (count != atoi(buff2))
136                                 elprintf(EL_STATUS, "cue: track index mismatch: track %i is track %i in cue",
137                                         count, atoi(buff2));
138                         // check type
139                         get_token(buff+6+ret, buff2, sizeof(buff2));
140                         if      (strcmp(buff2, "MODE1/2352") == 0)
141                                 data->tracks[count].type = CT_BIN;
142                         else if (strcmp(buff2, "MODE1/2048") == 0)
143                                 data->tracks[count].type = CT_ISO;
144                         else if (strcmp(buff2, "AUDIO") == 0)
145                         {
146                                 if (data->tracks[count].fname != NULL)
147                                 {
148                                         // rely on extension, not type in cue..
149                                         char *ext = get_ext(data->tracks[count].fname);
150                                         if      (strcasecmp(ext, "mp3") == 0)
151                                                 data->tracks[count].type = CT_MP3;
152                                         else if (strcasecmp(ext, "wav") == 0)
153                                                 data->tracks[count].type = CT_WAV;
154                                         else {
155                                                 elprintf(EL_STATUS, "unhandled audio format: \"%s\"",
156                                                         data->tracks[count].fname);
157                                         }
158                                 }
159                                 else
160                                 {
161                                         // propagate previous
162                                         data->tracks[count].type = data->tracks[count-1].type;
163                                 }
164                         }
165                         else {
166                                 elprintf(EL_STATUS, "unhandled track type: \"%s\"", buff2);
167                         }
168                 }
169                 else if (BEGINS(buff, "INDEX "))
170                 {
171                         int m, s, f;
172                         // type
173                         ret = get_token(buff+6, buff2, sizeof(buff2));
174                         if (atoi(buff2) == 0) continue;
175                         if (atoi(buff2) != 1) {
176                                 elprintf(EL_STATUS, "cue: don't know how to handle: \"%s\"", buff);
177                                 count--; break;
178                         }
179                         // offset in file
180                         get_token(buff+6+ret, buff2, sizeof(buff2));
181                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
182                         if (ret != 3) {
183                                 elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
184                                 count--; break;
185                         }
186                         data->tracks[count].sector_offset = m*60*75 + s*75 + f;
187                         // some strange .cues may need this
188                         if (data->tracks[count].fname != NULL && strcmp(data->tracks[count].fname, current_file) != 0)
189                         {
190                                 free(data->tracks[count].fname);
191                                 data->tracks[count].fname = strdup(current_file);
192                         }
193                         if (data->tracks[count].fname == NULL && strcmp(data->tracks[1].fname, current_file) != 0)
194                         {
195                                 data->tracks[count].fname = strdup(current_file);
196                         }
197                 }
198                 else if (BEGINS(buff, "PREGAP ") || BEGINS(buff, "POSTGAP "))
199                 {
200                         int m, s, f;
201                         get_token(buff+7, buff2, sizeof(buff2));
202                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
203                         if (ret != 3) {
204                                 elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff);
205                                 continue;
206                         }
207                         // pregap overrides previous postgap?
208                         // by looking at some .cues produced by some programs I've decided that..
209                         if (BEGINS(buff, "PREGAP "))
210                                 data->tracks[count].pregap = m*60*75 + s*75 + f;
211                         else
212                                 pending_pregap = m*60*75 + s*75 + f;
213                 }
214                 else if (BEGINS(buff, "REM LENGTH ")) // custom "extension"
215                 {
216                         int m, s, f;
217                         get_token(buff+11, buff2, sizeof(buff2));
218                         ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f);
219                         if (ret != 3) continue;
220                         data->tracks[count].sector_xlength = m*60*75 + s*75 + f;
221                 }
222                 else if (BEGINS(buff, "REM"))
223                         continue;
224                 else
225                 {
226                         elprintf(EL_STATUS, "cue: unhandled line: \"%s\"", buff);
227                 }
228         }
229
230         if (count < 1 || data->tracks[1].fname == NULL) {
231                 // failed..
232                 for (; count > 0; count--)
233                         if (data->tracks[count].fname != NULL)
234                                 free(data->tracks[count].fname);
235                 free(data);
236                 return NULL;
237         }
238
239         data->track_count = count;
240         return data;
241 }
242
243
244 void cue_destroy(cue_data_t *data)
245 {
246         int c;
247
248         if (data == NULL) return;
249
250         for (c = data->track_count; c > 0; c--)
251                 if (data->tracks[c].fname != NULL)
252                         free(data->tracks[c].fname);
253         free(data);
254 }
255
256
257 #if 0
258 int main(int argc, char *argv[])
259 {
260         cue_data_t *data = cue_parse(argv[1]);
261         int c;
262
263         if (data == NULL) return 1;
264
265         for (c = 1; c <= data->track_count; c++)
266                 printf("%2i: %i %9i %02i:%02i:%02i %9i %s\n", c, data->tracks[c].type, data->tracks[c].sector_offset,
267                         data->tracks[c].sector_offset / (75*60), data->tracks[c].sector_offset / 75 % 60,
268                         data->tracks[c].sector_offset % 75, data->tracks[c].pregap, data->tracks[c].fname);
269
270         cue_destroy(data);
271
272         return 0;
273 }
274 #endif
275