cff531af |
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 | */ |
b923ecbe |
8 | #include <stdio.h> |
9 | #include <stdlib.h> |
10 | #include <string.h> |
11 | #include "cue.h" |
12 | |
efcba75f |
13 | #include "../pico_int.h" |
9037e45d |
14 | // #define elprintf(w,f,...) printf(f "\n",##__VA_ARGS__); |
b923ecbe |
15 | |
67c81ee2 |
16 | #ifdef _MSC_VER |
17 | #define snprintf _snprintf |
18 | #endif |
f8af9634 |
19 | #ifdef __EPOC32__ |
ca482e5d |
20 | #define snprintf(b,s,...) sprintf(b,##__VA_ARGS__) |
21 | #endif |
67c81ee2 |
22 | |
b923ecbe |
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 | |
9037e45d |
59 | if (sep == '\"' && *p != sep) |
b923ecbe |
60 | elprintf(EL_STATUS, "cue: bad token: \"%s\"", buff); |
61 | |
62 | return d + skip; |
63 | } |
64 | |
e71fae1f |
65 | static int get_ext(const char *fname, char ext[4], |
66 | char *base, size_t base_size) |
b923ecbe |
67 | { |
e71fae1f |
68 | int len, pos = 0; |
69 | |
70 | len = strlen(fname); |
71 | if (len >= 3) |
72 | pos = len - 3; |
73 | |
74 | strcpy(ext, fname + pos); |
75 | |
76 | if (base != NULL) { |
77 | len = pos; |
78 | if (len + 1 < base_size) |
79 | len = base_size - 1; |
80 | memcpy(base, fname, len); |
81 | base[len] = 0; |
82 | } |
83 | return pos; |
84 | } |
85 | |
86 | static void change_case(char *p, int to_upper) |
87 | { |
88 | for (; *p != 0; p++) { |
89 | if (to_upper && 'a' <= *p && *p <= 'z') |
90 | *p += 'A' - 'a'; |
91 | else if (!to_upper && 'A' <= *p && *p <= 'Z') |
92 | *p += 'a' - 'A'; |
93 | } |
b923ecbe |
94 | } |
95 | |
e71fae1f |
96 | static int file_openable(const char *fname) |
97 | { |
98 | FILE *f = fopen(fname, "rb"); |
99 | if (f == NULL) |
100 | return 0; |
101 | fclose(f); |
102 | return 1; |
103 | } |
b923ecbe |
104 | |
105 | #define BEGINS(buff,str) (strncmp(buff,str,sizeof(str)-1) == 0) |
106 | |
107 | /* note: tracks[0] is not used */ |
9037e45d |
108 | cue_data_t *cue_parse(const char *fname) |
b923ecbe |
109 | { |
e71fae1f |
110 | char current_file[256], *current_filep, cue_base[256]; |
111 | char buff[256], buff2[32], ext[4], *p; |
c9e1affc |
112 | int ret, count = 0, count_alloc = 2, pending_pregap = 0; |
e71fae1f |
113 | size_t current_filep_size, fname_len; |
114 | cue_data_t *data = NULL; |
115 | FILE *f = NULL; |
b923ecbe |
116 | void *tmp; |
117 | |
e71fae1f |
118 | if (fname == NULL || (fname_len = strlen(fname)) == 0) |
119 | return NULL; |
120 | |
121 | ret = get_ext(fname, ext, cue_base, sizeof(cue_base)); |
122 | if (strcasecmp(ext, "cue") == 0) { |
123 | f = fopen(fname, "r"); |
124 | } |
125 | else { |
126 | // not a .cue, try one with the same base name |
127 | if (ret + 3 < sizeof(cue_base)) { |
128 | strcpy(cue_base + ret, "cue"); |
129 | f = fopen(cue_base, "r"); |
130 | if (f == NULL) { |
131 | strcpy(cue_base + ret, "CUE"); |
132 | f = fopen(cue_base, "r"); |
133 | } |
134 | } |
135 | } |
136 | |
137 | if (f == NULL) |
138 | return NULL; |
b923ecbe |
139 | |
c9e1affc |
140 | snprintf(current_file, sizeof(current_file), "%s", fname); |
e71fae1f |
141 | current_filep = current_file + strlen(current_file); |
142 | for (; current_filep > current_file; current_filep--) |
143 | if (current_filep[-1] == '/' || current_filep[-1] == '\\') |
144 | break; |
145 | |
146 | current_filep_size = sizeof(current_file) - (current_filep - current_file); |
147 | |
148 | // the basename of cuefile, no path |
149 | snprintf(cue_base, sizeof(cue_base), "%s", current_filep); |
150 | p = cue_base + strlen(cue_base); |
151 | if (p - 3 >= cue_base) |
152 | p[-3] = 0; |
c9e1affc |
153 | |
b923ecbe |
154 | data = calloc(1, sizeof(*data) + count_alloc * sizeof(cue_track)); |
e71fae1f |
155 | if (data == NULL) |
156 | goto out; |
b923ecbe |
157 | |
158 | while (!feof(f)) |
159 | { |
160 | tmp = fgets(buff, sizeof(buff), f); |
e71fae1f |
161 | if (tmp == NULL) |
162 | break; |
b923ecbe |
163 | |
164 | mystrip(buff); |
e71fae1f |
165 | if (buff[0] == 0) |
166 | continue; |
0bccafeb |
167 | if (BEGINS(buff, "TITLE ") || BEGINS(buff, "PERFORMER ") || BEGINS(buff, "SONGWRITER ")) |
b923ecbe |
168 | continue; /* who would put those here? Ignore! */ |
169 | else if (BEGINS(buff, "FILE ")) |
170 | { |
e71fae1f |
171 | get_token(buff + 5, current_filep, current_filep_size); |
b923ecbe |
172 | } |
173 | else if (BEGINS(buff, "TRACK ")) |
174 | { |
175 | count++; |
176 | if (count >= count_alloc) { |
177 | count_alloc *= 2; |
178 | tmp = realloc(data, sizeof(*data) + count_alloc * sizeof(cue_track)); |
e71fae1f |
179 | if (tmp == NULL) { |
180 | count--; |
181 | break; |
182 | } |
9037e45d |
183 | data = tmp; |
b923ecbe |
184 | } |
185 | memset(&data->tracks[count], 0, sizeof(data->tracks[0])); |
e71fae1f |
186 | |
b923ecbe |
187 | if (count == 1 || strcmp(data->tracks[1].fname, current_file) != 0) |
188 | { |
e71fae1f |
189 | if (file_openable(current_file)) |
190 | goto file_ok; |
191 | |
192 | elprintf(EL_STATUS, "cue: bad/missing file: \"%s\"", current_file); |
193 | if (count == 1) { |
194 | int cue_ucase; |
195 | char v; |
196 | |
197 | get_ext(current_file, ext, NULL, 0); |
198 | snprintf(current_filep, current_filep_size, |
199 | "%s%s", cue_base, ext); |
200 | if (file_openable(current_file)) |
201 | goto file_ok; |
202 | |
203 | // try with the same case (for unix) |
204 | v = fname[fname_len - 1]; |
205 | cue_ucase = ('A' <= v && v <= 'Z'); |
206 | change_case(ext, cue_ucase); |
207 | |
208 | snprintf(current_filep, current_filep_size, |
209 | "%s%s", cue_base, ext); |
210 | if (file_openable(current_file)) |
211 | goto file_ok; |
b923ecbe |
212 | } |
e71fae1f |
213 | |
214 | count--; |
215 | break; |
216 | |
217 | file_ok: |
218 | data->tracks[count].fname = strdup(current_file); |
219 | if (data->tracks[count].fname == NULL) |
220 | break; |
b923ecbe |
221 | } |
c9e1affc |
222 | data->tracks[count].pregap = pending_pregap; |
223 | pending_pregap = 0; |
b923ecbe |
224 | // track number |
225 | ret = get_token(buff+6, buff2, sizeof(buff2)); |
226 | if (count != atoi(buff2)) |
227 | elprintf(EL_STATUS, "cue: track index mismatch: track %i is track %i in cue", |
228 | count, atoi(buff2)); |
229 | // check type |
230 | get_token(buff+6+ret, buff2, sizeof(buff2)); |
9037e45d |
231 | if (strcmp(buff2, "MODE1/2352") == 0) |
b923ecbe |
232 | data->tracks[count].type = CT_BIN; |
9037e45d |
233 | else if (strcmp(buff2, "MODE1/2048") == 0) |
b923ecbe |
234 | data->tracks[count].type = CT_ISO; |
9037e45d |
235 | else if (strcmp(buff2, "AUDIO") == 0) |
b923ecbe |
236 | { |
237 | if (data->tracks[count].fname != NULL) |
238 | { |
239 | // rely on extension, not type in cue.. |
e71fae1f |
240 | get_ext(data->tracks[count].fname, ext, NULL, 0); |
b923ecbe |
241 | if (strcasecmp(ext, "mp3") == 0) |
242 | data->tracks[count].type = CT_MP3; |
243 | else if (strcasecmp(ext, "wav") == 0) |
244 | data->tracks[count].type = CT_WAV; |
274fcc35 |
245 | else if (strcasecmp(ext, "bin") == 0) |
246 | data->tracks[count].type = CT_BIN; |
b923ecbe |
247 | else { |
248 | elprintf(EL_STATUS, "unhandled audio format: \"%s\"", |
249 | data->tracks[count].fname); |
250 | } |
251 | } |
9037e45d |
252 | else |
253 | { |
254 | // propagate previous |
255 | data->tracks[count].type = data->tracks[count-1].type; |
256 | } |
b923ecbe |
257 | } |
258 | else { |
259 | elprintf(EL_STATUS, "unhandled track type: \"%s\"", buff2); |
260 | } |
261 | } |
262 | else if (BEGINS(buff, "INDEX ")) |
263 | { |
264 | int m, s, f; |
265 | // type |
266 | ret = get_token(buff+6, buff2, sizeof(buff2)); |
267 | if (atoi(buff2) == 0) continue; |
268 | if (atoi(buff2) != 1) { |
269 | elprintf(EL_STATUS, "cue: don't know how to handle: \"%s\"", buff); |
270 | count--; break; |
271 | } |
272 | // offset in file |
273 | get_token(buff+6+ret, buff2, sizeof(buff2)); |
9037e45d |
274 | ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f); |
b923ecbe |
275 | if (ret != 3) { |
276 | elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff); |
277 | count--; break; |
278 | } |
279 | data->tracks[count].sector_offset = m*60*75 + s*75 + f; |
9037e45d |
280 | // some strange .cues may need this |
281 | if (data->tracks[count].fname != NULL && strcmp(data->tracks[count].fname, current_file) != 0) |
282 | { |
283 | free(data->tracks[count].fname); |
284 | data->tracks[count].fname = strdup(current_file); |
285 | } |
286 | if (data->tracks[count].fname == NULL && strcmp(data->tracks[1].fname, current_file) != 0) |
287 | { |
288 | data->tracks[count].fname = strdup(current_file); |
289 | } |
b923ecbe |
290 | } |
c9e1affc |
291 | else if (BEGINS(buff, "PREGAP ") || BEGINS(buff, "POSTGAP ")) |
b923ecbe |
292 | { |
293 | int m, s, f; |
294 | get_token(buff+7, buff2, sizeof(buff2)); |
9037e45d |
295 | ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f); |
b923ecbe |
296 | if (ret != 3) { |
297 | elprintf(EL_STATUS, "cue: failed to parse: \"%s\"", buff); |
298 | continue; |
299 | } |
c9e1affc |
300 | // pregap overrides previous postgap? |
301 | // by looking at some .cues produced by some programs I've decided that.. |
302 | if (BEGINS(buff, "PREGAP ")) |
303 | data->tracks[count].pregap = m*60*75 + s*75 + f; |
304 | else |
305 | pending_pregap = m*60*75 + s*75 + f; |
b923ecbe |
306 | } |
0bccafeb |
307 | else if (BEGINS(buff, "REM LENGTH ")) // custom "extension" |
308 | { |
309 | int m, s, f; |
310 | get_token(buff+11, buff2, sizeof(buff2)); |
311 | ret = sscanf(buff2, "%d:%d:%d", &m, &s, &f); |
312 | if (ret != 3) continue; |
313 | data->tracks[count].sector_xlength = m*60*75 + s*75 + f; |
314 | } |
315 | else if (BEGINS(buff, "REM")) |
316 | continue; |
b923ecbe |
317 | else |
318 | { |
9037e45d |
319 | elprintf(EL_STATUS, "cue: unhandled line: \"%s\"", buff); |
b923ecbe |
320 | } |
321 | } |
322 | |
323 | if (count < 1 || data->tracks[1].fname == NULL) { |
324 | // failed.. |
325 | for (; count > 0; count--) |
326 | if (data->tracks[count].fname != NULL) |
327 | free(data->tracks[count].fname); |
328 | free(data); |
e71fae1f |
329 | data = NULL; |
330 | goto out; |
b923ecbe |
331 | } |
332 | |
333 | data->track_count = count; |
e71fae1f |
334 | |
335 | out: |
336 | if (f != NULL) |
337 | fclose(f); |
b923ecbe |
338 | return data; |
339 | } |
340 | |
341 | |
9037e45d |
342 | void cue_destroy(cue_data_t *data) |
b923ecbe |
343 | { |
344 | int c; |
345 | |
346 | if (data == NULL) return; |
347 | |
348 | for (c = data->track_count; c > 0; c--) |
349 | if (data->tracks[c].fname != NULL) |
350 | free(data->tracks[c].fname); |
351 | free(data); |
352 | } |
353 | |
354 | |
9037e45d |
355 | #if 0 |
b923ecbe |
356 | int main(int argc, char *argv[]) |
357 | { |
9037e45d |
358 | cue_data_t *data = cue_parse(argv[1]); |
b923ecbe |
359 | int c; |
360 | |
361 | if (data == NULL) return 1; |
362 | |
363 | for (c = 1; c <= data->track_count; c++) |
9037e45d |
364 | printf("%2i: %i %9i %02i:%02i:%02i %9i %s\n", c, data->tracks[c].type, data->tracks[c].sector_offset, |
365 | data->tracks[c].sector_offset / (75*60), data->tracks[c].sector_offset / 75 % 60, |
366 | data->tracks[c].sector_offset % 75, data->tracks[c].pregap, data->tracks[c].fname); |
b923ecbe |
367 | |
368 | cue_destroy(data); |
369 | |
370 | return 0; |
371 | } |
9037e45d |
372 | #endif |
b923ecbe |
373 | |