Commit | Line | Data |
---|---|---|
ef79bbde P |
1 | /*************************************************************************** |
2 | * Copyright (C) 2007 PCSX-df Team * | |
3 | * Copyright (C) 2009 Wei Mingzhi * | |
4 | * * | |
5 | * This program is free software; you can redistribute it and/or modify * | |
6 | * it under the terms of the GNU General Public License as published by * | |
7 | * the Free Software Foundation; either version 2 of the License, or * | |
8 | * (at your option) any later version. * | |
9 | * * | |
10 | * This program is distributed in the hope that it will be useful, * | |
11 | * but WITHOUT ANY WARRANTY; without even the implied warranty of * | |
12 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * | |
13 | * GNU General Public License for more details. * | |
14 | * * | |
15 | * You should have received a copy of the GNU General Public License * | |
16 | * along with this program; if not, write to the * | |
17 | * Free Software Foundation, Inc., * | |
18 | * 51 Franklin Street, Fifth Floor, Boston, MA 02111-1307 USA. * | |
19 | ***************************************************************************/ | |
20 | ||
21 | #include "psxcommon.h" | |
22 | #include "plugins.h" | |
23 | #include "cdrom.h" | |
24 | #include "cdriso.h" | |
25 | ||
26 | #ifdef _WIN32 | |
27 | #include <process.h> | |
28 | #include <windows.h> | |
29 | #else | |
30 | #include <pthread.h> | |
31 | #include <sys/time.h> | |
32 | #endif | |
33 | ||
34 | static FILE *cdHandle = NULL; | |
35 | static FILE *cddaHandle = NULL; | |
36 | static FILE *subHandle = NULL; | |
37 | ||
38 | static boolean subChanMixed = FALSE; | |
39 | static boolean subChanRaw = FALSE; | |
40 | ||
41 | static unsigned char cdbuffer[DATA_SIZE]; | |
42 | static unsigned char subbuffer[SUB_FRAMESIZE]; | |
43 | ||
44 | static unsigned char sndbuffer[CD_FRAMESIZE_RAW * 10]; | |
45 | ||
46 | #define CDDA_FRAMETIME (1000 * (sizeof(sndbuffer) / CD_FRAMESIZE_RAW) / 75) | |
47 | ||
48 | #ifdef _WIN32 | |
49 | static HANDLE threadid; | |
50 | #else | |
51 | static pthread_t threadid; | |
52 | #endif | |
53 | static unsigned int initial_offset = 0; | |
38b8102c | 54 | static boolean playing = FALSE; |
ef79bbde | 55 | static boolean cddaBigEndian = FALSE; |
38b8102c | 56 | static unsigned int cddaCurOffset = 0; |
57 | static unsigned int cddaStartOffset; | |
ef79bbde P |
58 | |
59 | char* CALLBACK CDR__getDriveLetter(void); | |
60 | long CALLBACK CDR__configure(void); | |
61 | long CALLBACK CDR__test(void); | |
62 | void CALLBACK CDR__about(void); | |
63 | long CALLBACK CDR__setfilename(char *filename); | |
64 | long CALLBACK CDR__getStatus(struct CdrStat *stat); | |
65 | ||
66 | extern void *hCDRDriver; | |
67 | ||
68 | struct trackinfo { | |
69 | enum {DATA, CDDA} type; | |
70 | char start[3]; // MSF-format | |
71 | char length[3]; // MSF-format | |
38b8102c | 72 | FILE *handle; // for multi-track images CDDA |
ef79bbde P |
73 | }; |
74 | ||
75 | #define MAXTRACKS 100 /* How many tracks can a CD hold? */ | |
76 | ||
77 | static int numtracks = 0; | |
78 | static struct trackinfo ti[MAXTRACKS]; | |
79 | ||
80 | // get a sector from a msf-array | |
81 | static unsigned int msf2sec(char *msf) { | |
82 | return ((msf[0] * 60 + msf[1]) * 75) + msf[2]; | |
83 | } | |
84 | ||
85 | static void sec2msf(unsigned int s, char *msf) { | |
86 | msf[0] = s / 75 / 60; | |
87 | s = s - msf[0] * 75 * 60; | |
88 | msf[1] = s / 75; | |
89 | s = s - msf[1] * 75; | |
90 | msf[2] = s; | |
91 | } | |
92 | ||
93 | // divide a string of xx:yy:zz into m, s, f | |
94 | static void tok2msf(char *time, char *msf) { | |
95 | char *token; | |
96 | ||
97 | token = strtok(time, ":"); | |
98 | if (token) { | |
99 | msf[0] = atoi(token); | |
100 | } | |
101 | else { | |
102 | msf[0] = 0; | |
103 | } | |
104 | ||
105 | token = strtok(NULL, ":"); | |
106 | if (token) { | |
107 | msf[1] = atoi(token); | |
108 | } | |
109 | else { | |
110 | msf[1] = 0; | |
111 | } | |
112 | ||
113 | token = strtok(NULL, ":"); | |
114 | if (token) { | |
115 | msf[2] = atoi(token); | |
116 | } | |
117 | else { | |
118 | msf[2] = 0; | |
119 | } | |
120 | } | |
121 | ||
122 | #ifndef _WIN32 | |
123 | static long GetTickCount(void) { | |
124 | static time_t initial_time = 0; | |
125 | struct timeval now; | |
126 | ||
127 | gettimeofday(&now, NULL); | |
128 | ||
129 | if (initial_time == 0) { | |
130 | initial_time = now.tv_sec; | |
131 | } | |
132 | ||
133 | return (now.tv_sec - initial_time) * 1000L + now.tv_usec / 1000L; | |
134 | } | |
135 | #endif | |
136 | ||
137 | // this thread plays audio data | |
138 | #ifdef _WIN32 | |
139 | static void playthread(void *param) | |
140 | #else | |
141 | static void *playthread(void *param) | |
142 | #endif | |
143 | { | |
144 | long d, t, i, s; | |
145 | unsigned char tmp; | |
146 | ||
147 | t = GetTickCount(); | |
148 | ||
149 | while (playing) { | |
150 | d = t - (long)GetTickCount(); | |
151 | if (d <= 0) { | |
152 | d = 1; | |
153 | } | |
154 | else if (d > CDDA_FRAMETIME) { | |
155 | d = CDDA_FRAMETIME; | |
156 | } | |
157 | #ifdef _WIN32 | |
158 | Sleep(d); | |
159 | #else | |
160 | usleep(d * 1000); | |
161 | #endif | |
162 | ||
163 | t = GetTickCount() + CDDA_FRAMETIME; | |
164 | ||
165 | if (subChanMixed) { | |
166 | s = 0; | |
167 | ||
168 | for (i = 0; i < sizeof(sndbuffer) / CD_FRAMESIZE_RAW; i++) { | |
169 | // read one sector | |
170 | d = fread(sndbuffer + CD_FRAMESIZE_RAW * i, 1, CD_FRAMESIZE_RAW, cddaHandle); | |
171 | if (d < CD_FRAMESIZE_RAW) { | |
172 | break; | |
173 | } | |
174 | ||
175 | s += d; | |
176 | ||
177 | // skip the subchannel data | |
178 | fseek(cddaHandle, SUB_FRAMESIZE, SEEK_CUR); | |
179 | } | |
180 | } | |
181 | else { | |
182 | s = fread(sndbuffer, 1, sizeof(sndbuffer), cddaHandle); | |
183 | } | |
184 | ||
185 | if (s == 0) { | |
186 | playing = FALSE; | |
ef79bbde P |
187 | initial_offset = 0; |
188 | break; | |
189 | } | |
190 | ||
191 | if (!cdr.Muted && playing) { | |
192 | if (cddaBigEndian) { | |
193 | for (i = 0; i < s / 2; i++) { | |
194 | tmp = sndbuffer[i * 2]; | |
195 | sndbuffer[i * 2] = sndbuffer[i * 2 + 1]; | |
196 | sndbuffer[i * 2 + 1] = tmp; | |
197 | } | |
198 | } | |
199 | ||
200 | SPU_playCDDAchannel((short *)sndbuffer, s); | |
201 | } | |
202 | ||
203 | cddaCurOffset += s; | |
204 | } | |
205 | ||
206 | #ifdef _WIN32 | |
207 | _endthread(); | |
208 | #else | |
209 | pthread_exit(0); | |
210 | return NULL; | |
211 | #endif | |
212 | } | |
213 | ||
214 | // stop the CDDA playback | |
215 | static void stopCDDA() { | |
216 | if (!playing) { | |
217 | return; | |
218 | } | |
219 | ||
220 | playing = FALSE; | |
221 | #ifdef _WIN32 | |
222 | WaitForSingleObject(threadid, INFINITE); | |
223 | #else | |
224 | pthread_join(threadid, NULL); | |
225 | #endif | |
226 | ||
ef79bbde P |
227 | initial_offset = 0; |
228 | } | |
229 | ||
230 | // start the CDDA playback | |
231 | static void startCDDA(unsigned int offset) { | |
232 | if (playing) { | |
233 | if (initial_offset == offset) { | |
234 | return; | |
235 | } | |
236 | stopCDDA(); | |
237 | } | |
238 | ||
ef79bbde P |
239 | initial_offset = offset; |
240 | cddaCurOffset = initial_offset; | |
241 | fseek(cddaHandle, initial_offset, SEEK_SET); | |
242 | ||
243 | playing = TRUE; | |
244 | ||
245 | #ifdef _WIN32 | |
246 | threadid = (HANDLE)_beginthread(playthread, 0, NULL); | |
247 | #else | |
248 | pthread_create(&threadid, NULL, playthread, NULL); | |
249 | #endif | |
250 | } | |
251 | ||
252 | // this function tries to get the .toc file of the given .bin | |
253 | // the necessary data is put into the ti (trackinformation)-array | |
254 | static int parsetoc(const char *isofile) { | |
255 | char tocname[MAXPATHLEN]; | |
256 | FILE *fi; | |
257 | char linebuf[256], dummy[256], name[256]; | |
258 | char *token; | |
259 | char time[20], time2[20]; | |
260 | unsigned int t; | |
261 | ||
262 | numtracks = 0; | |
263 | ||
264 | // copy name of the iso and change extension from .bin to .toc | |
265 | strncpy(tocname, isofile, sizeof(tocname)); | |
266 | tocname[MAXPATHLEN - 1] = '\0'; | |
267 | if (strlen(tocname) >= 4) { | |
268 | strcpy(tocname + strlen(tocname) - 4, ".toc"); | |
269 | } | |
270 | else { | |
271 | return -1; | |
272 | } | |
273 | ||
274 | if ((fi = fopen(tocname, "r")) == NULL) { | |
275 | // try changing extension to .cue (to satisfy some stupid tutorials) | |
276 | strcpy(tocname + strlen(tocname) - 4, ".cue"); | |
277 | if ((fi = fopen(tocname, "r")) == NULL) { | |
278 | // if filename is image.toc.bin, try removing .bin (for Brasero) | |
279 | strcpy(tocname, isofile); | |
280 | t = strlen(tocname); | |
281 | if (t >= 8 && strcmp(tocname + t - 8, ".toc.bin") == 0) { | |
282 | tocname[t - 4] = '\0'; | |
283 | if ((fi = fopen(tocname, "r")) == NULL) { | |
284 | return -1; | |
285 | } | |
286 | } | |
287 | else { | |
288 | return -1; | |
289 | } | |
290 | } | |
291 | } | |
292 | ||
293 | memset(&ti, 0, sizeof(ti)); | |
294 | cddaBigEndian = TRUE; // cdrdao uses big-endian for CD Audio | |
295 | ||
296 | // parse the .toc file | |
297 | while (fgets(linebuf, sizeof(linebuf), fi) != NULL) { | |
298 | // search for tracks | |
299 | strncpy(dummy, linebuf, sizeof(linebuf)); | |
300 | token = strtok(dummy, " "); | |
301 | ||
302 | if (token == NULL) continue; | |
303 | ||
304 | if (!strcmp(token, "TRACK")) { | |
305 | // get type of track | |
306 | token = strtok(NULL, " "); | |
307 | numtracks++; | |
308 | ||
309 | if (!strncmp(token, "MODE2_RAW", 9)) { | |
310 | ti[numtracks].type = DATA; | |
311 | sec2msf(2 * 75, ti[numtracks].start); // assume data track on 0:2:0 | |
312 | ||
313 | // check if this image contains mixed subchannel data | |
314 | token = strtok(NULL, " "); | |
315 | if (token != NULL && !strncmp(token, "RW_RAW", 6)) { | |
316 | subChanMixed = TRUE; | |
317 | subChanRaw = TRUE; | |
318 | } | |
319 | } | |
320 | else if (!strncmp(token, "AUDIO", 5)) { | |
321 | ti[numtracks].type = CDDA; | |
322 | } | |
323 | } | |
324 | else if (!strcmp(token, "DATAFILE")) { | |
325 | if (ti[numtracks].type == CDDA) { | |
326 | sscanf(linebuf, "DATAFILE \"%[^\"]\" #%d %8s", name, &t, time2); | |
327 | t /= CD_FRAMESIZE_RAW + (subChanMixed ? SUB_FRAMESIZE : 0); | |
328 | t += 2 * 75; | |
329 | sec2msf(t, (char *)&ti[numtracks].start); | |
330 | tok2msf((char *)&time2, (char *)&ti[numtracks].length); | |
331 | } | |
332 | else { | |
333 | sscanf(linebuf, "DATAFILE \"%[^\"]\" %8s", name, time); | |
334 | tok2msf((char *)&time, (char *)&ti[numtracks].length); | |
335 | } | |
336 | } | |
337 | else if (!strcmp(token, "FILE")) { | |
338 | sscanf(linebuf, "FILE \"%[^\"]\" #%d %8s %8s", name, &t, time, time2); | |
339 | tok2msf((char *)&time, (char *)&ti[numtracks].start); | |
340 | t /= CD_FRAMESIZE_RAW + (subChanMixed ? SUB_FRAMESIZE : 0); | |
341 | t += msf2sec(ti[numtracks].start) + 2 * 75; | |
342 | sec2msf(t, (char *)&ti[numtracks].start); | |
343 | tok2msf((char *)&time2, (char *)&ti[numtracks].length); | |
344 | } | |
345 | } | |
346 | ||
347 | fclose(fi); | |
348 | ||
349 | return 0; | |
350 | } | |
351 | ||
352 | // this function tries to get the .cue file of the given .bin | |
353 | // the necessary data is put into the ti (trackinformation)-array | |
354 | static int parsecue(const char *isofile) { | |
355 | char cuename[MAXPATHLEN]; | |
38b8102c | 356 | char filepath[MAXPATHLEN]; |
357 | char *incue_fname; | |
ef79bbde P |
358 | FILE *fi; |
359 | char *token; | |
360 | char time[20]; | |
361 | char *tmp; | |
362 | char linebuf[256], dummy[256]; | |
38b8102c | 363 | unsigned int incue_max_len; |
364 | unsigned int t, i, total, file_len; | |
ef79bbde P |
365 | |
366 | numtracks = 0; | |
367 | ||
368 | // copy name of the iso and change extension from .bin to .cue | |
369 | strncpy(cuename, isofile, sizeof(cuename)); | |
370 | cuename[MAXPATHLEN - 1] = '\0'; | |
371 | if (strlen(cuename) >= 4) { | |
372 | strcpy(cuename + strlen(cuename) - 4, ".cue"); | |
373 | } | |
374 | else { | |
375 | return -1; | |
376 | } | |
377 | ||
378 | if ((fi = fopen(cuename, "r")) == NULL) { | |
379 | return -1; | |
380 | } | |
381 | ||
382 | // Some stupid tutorials wrongly tell users to use cdrdao to rip a | |
383 | // "bin/cue" image, which is in fact a "bin/toc" image. So let's check | |
384 | // that... | |
385 | if (fgets(linebuf, sizeof(linebuf), fi) != NULL) { | |
386 | if (!strncmp(linebuf, "CD_ROM_XA", 9)) { | |
387 | // Don't proceed further, as this is actually a .toc file rather | |
388 | // than a .cue file. | |
389 | fclose(fi); | |
390 | return parsetoc(isofile); | |
391 | } | |
392 | fseek(fi, 0, SEEK_SET); | |
393 | } | |
394 | ||
38b8102c | 395 | // build a path for files referenced in .cue |
396 | strncpy(filepath, cuename, sizeof(filepath)); | |
397 | tmp = strrchr(filepath, '/') + 1; | |
398 | if (tmp == NULL) | |
399 | tmp = strrchr(filepath, '\\') + 1; | |
400 | if (tmp == NULL) | |
401 | tmp = filepath; | |
402 | *tmp = 0; | |
403 | filepath[sizeof(filepath) - 1] = 0; | |
404 | incue_fname = tmp; | |
405 | incue_max_len = sizeof(filepath) - (tmp - filepath) - 1; | |
406 | ||
ef79bbde P |
407 | memset(&ti, 0, sizeof(ti)); |
408 | ||
409 | while (fgets(linebuf, sizeof(linebuf), fi) != NULL) { | |
410 | strncpy(dummy, linebuf, sizeof(linebuf)); | |
411 | token = strtok(dummy, " "); | |
412 | ||
413 | if (token == NULL) { | |
414 | continue; | |
415 | } | |
416 | ||
417 | if (!strcmp(token, "TRACK")){ | |
418 | numtracks++; | |
419 | ||
420 | if (strstr(linebuf, "AUDIO") != NULL) { | |
421 | ti[numtracks].type = CDDA; | |
422 | } | |
423 | else if (strstr(linebuf, "MODE1/2352") != NULL || strstr(linebuf, "MODE2/2352") != NULL) { | |
424 | ti[numtracks].type = DATA; | |
425 | } | |
426 | } | |
427 | else if (!strcmp(token, "INDEX")) { | |
428 | tmp = strstr(linebuf, "INDEX"); | |
429 | if (tmp != NULL) { | |
430 | tmp += strlen("INDEX") + 3; // 3 - space + numeric index | |
431 | while (*tmp == ' ') tmp++; | |
432 | if (*tmp != '\n') sscanf(tmp, "%8s", time); | |
433 | } | |
434 | ||
435 | tok2msf((char *)&time, (char *)&ti[numtracks].start); | |
436 | ||
437 | t = msf2sec(ti[numtracks].start) + 2 * 75; | |
438 | sec2msf(t, ti[numtracks].start); | |
38b8102c | 439 | } |
440 | else if (!strcmp(token, "FILE")) { | |
441 | tmp = strstr(linebuf, "FILE"); | |
442 | if (tmp == NULL) { | |
443 | continue; | |
444 | } | |
445 | tmp += 4; | |
446 | while (*tmp == ' ') | |
447 | tmp++; | |
448 | if (*tmp == '"') { | |
449 | token = tmp + 1; | |
450 | tmp = strchr(token, '"'); | |
451 | if (tmp != NULL) | |
452 | *tmp = 0; | |
453 | } | |
454 | else { | |
455 | token = tmp; | |
456 | tmp = strchr(token, ' '); | |
457 | if (tmp != NULL) | |
458 | *tmp = 0; | |
459 | } | |
ef79bbde | 460 | |
38b8102c | 461 | strncpy(incue_fname, token, incue_max_len); |
462 | ti[numtracks + 1].handle = fopen(filepath, "rb"); | |
463 | if (ti[numtracks + 1].handle == NULL) { | |
464 | SysPrintf(_("could not open: %s\n"), filepath); | |
465 | } | |
466 | else if (numtracks == 0 && strlen(isofile) >= 4 && | |
467 | strcmp(isofile + strlen(isofile) - 4, ".cue") == 0) { | |
468 | // user selected .cue as image file, use it's data track instead | |
469 | fclose(cdHandle); | |
470 | cdHandle = fopen(filepath, "rb"); | |
ef79bbde P |
471 | } |
472 | } | |
473 | } | |
474 | ||
475 | fclose(fi); | |
476 | ||
38b8102c | 477 | // make corrections for multi-track .cue, fill track lengths |
478 | total = 2 * 75; | |
479 | file_len = 0; | |
480 | for (i = 1; i <= numtracks; i++) { | |
481 | if (ti[i].handle != NULL) { | |
482 | sec2msf(total, ti[i].start); | |
483 | fseek(ti[i].handle, 0, SEEK_END); | |
484 | file_len = ftell(ti[i].handle) / 2352; | |
485 | sec2msf(file_len, ti[i].length); | |
486 | total += file_len; | |
487 | } | |
488 | else { | |
489 | // this track uses the same file as the last, | |
490 | // start of this track is last track's end | |
491 | if (i > 1) { | |
492 | t = msf2sec(ti[i].start) - msf2sec(ti[i - 1].start); | |
493 | sec2msf(t, ti[i - 1].length); | |
494 | } | |
495 | t = file_len - msf2sec(ti[i].start) + 2 * 75; | |
496 | sec2msf(t, ti[i].length); | |
497 | } | |
ef79bbde P |
498 | } |
499 | ||
500 | return 0; | |
501 | } | |
502 | ||
503 | // this function tries to get the .ccd file of the given .img | |
504 | // the necessary data is put into the ti (trackinformation)-array | |
505 | static int parseccd(const char *isofile) { | |
506 | char ccdname[MAXPATHLEN]; | |
507 | FILE *fi; | |
508 | char linebuf[256]; | |
509 | unsigned int t; | |
510 | ||
511 | numtracks = 0; | |
512 | ||
513 | // copy name of the iso and change extension from .img to .ccd | |
514 | strncpy(ccdname, isofile, sizeof(ccdname)); | |
515 | ccdname[MAXPATHLEN - 1] = '\0'; | |
516 | if (strlen(ccdname) >= 4) { | |
517 | strcpy(ccdname + strlen(ccdname) - 4, ".ccd"); | |
518 | } | |
519 | else { | |
520 | return -1; | |
521 | } | |
522 | ||
523 | if ((fi = fopen(ccdname, "r")) == NULL) { | |
524 | return -1; | |
525 | } | |
526 | ||
527 | memset(&ti, 0, sizeof(ti)); | |
528 | ||
529 | while (fgets(linebuf, sizeof(linebuf), fi) != NULL) { | |
530 | if (!strncmp(linebuf, "[TRACK", 6)){ | |
531 | numtracks++; | |
532 | } | |
533 | else if (!strncmp(linebuf, "MODE=", 5)) { | |
534 | sscanf(linebuf, "MODE=%d", &t); | |
535 | ti[numtracks].type = ((t == 0) ? CDDA : DATA); | |
536 | } | |
537 | else if (!strncmp(linebuf, "INDEX 1=", 8)) { | |
538 | sscanf(linebuf, "INDEX 1=%d", &t); | |
539 | sec2msf(t + 2 * 75, ti[numtracks].start); | |
540 | ||
541 | // If we've already seen another track, this is its end | |
542 | if (numtracks > 1) { | |
543 | t = msf2sec(ti[numtracks].start) - msf2sec(ti[numtracks - 1].start); | |
544 | sec2msf(t, ti[numtracks - 1].length); | |
545 | } | |
546 | } | |
547 | } | |
548 | ||
549 | fclose(fi); | |
550 | ||
551 | // Fill out the last track's end based on size | |
552 | if (numtracks >= 1) { | |
553 | fseek(cdHandle, 0, SEEK_END); | |
554 | t = ftell(cdHandle) / 2352 - msf2sec(ti[numtracks].start) + 2 * 75; | |
555 | sec2msf(t, ti[numtracks].length); | |
556 | } | |
557 | ||
558 | return 0; | |
559 | } | |
560 | ||
561 | // this function tries to get the .mds file of the given .mdf | |
562 | // the necessary data is put into the ti (trackinformation)-array | |
563 | static int parsemds(const char *isofile) { | |
564 | char mdsname[MAXPATHLEN]; | |
565 | FILE *fi; | |
566 | unsigned int offset, extra_offset, l, i; | |
567 | unsigned short s; | |
568 | ||
569 | numtracks = 0; | |
570 | ||
571 | // copy name of the iso and change extension from .mdf to .mds | |
572 | strncpy(mdsname, isofile, sizeof(mdsname)); | |
573 | mdsname[MAXPATHLEN - 1] = '\0'; | |
574 | if (strlen(mdsname) >= 4) { | |
575 | strcpy(mdsname + strlen(mdsname) - 4, ".mds"); | |
576 | } | |
577 | else { | |
578 | return -1; | |
579 | } | |
580 | ||
581 | if ((fi = fopen(mdsname, "rb")) == NULL) { | |
582 | return -1; | |
583 | } | |
584 | ||
585 | memset(&ti, 0, sizeof(ti)); | |
586 | ||
587 | // check if it's a valid mds file | |
588 | fread(&i, 1, sizeof(unsigned int), fi); | |
589 | i = SWAP32(i); | |
590 | if (i != 0x4944454D) { | |
591 | // not an valid mds file | |
592 | fclose(fi); | |
593 | return -1; | |
594 | } | |
595 | ||
596 | // get offset to session block | |
597 | fseek(fi, 0x50, SEEK_SET); | |
598 | fread(&offset, 1, sizeof(unsigned int), fi); | |
599 | offset = SWAP32(offset); | |
600 | ||
601 | // get total number of tracks | |
602 | offset += 14; | |
603 | fseek(fi, offset, SEEK_SET); | |
604 | fread(&s, 1, sizeof(unsigned short), fi); | |
605 | s = SWAP16(s); | |
606 | numtracks = s; | |
607 | ||
608 | // get offset to track blocks | |
609 | fseek(fi, 4, SEEK_CUR); | |
610 | fread(&offset, 1, sizeof(unsigned int), fi); | |
611 | offset = SWAP32(offset); | |
612 | ||
613 | // skip lead-in data | |
614 | while (1) { | |
615 | fseek(fi, offset + 4, SEEK_SET); | |
616 | if (fgetc(fi) < 0xA0) { | |
617 | break; | |
618 | } | |
619 | offset += 0x50; | |
620 | } | |
621 | ||
622 | // check if the image contains mixed subchannel data | |
623 | fseek(fi, offset + 1, SEEK_SET); | |
624 | subChanMixed = (fgetc(fi) ? TRUE : FALSE); | |
625 | ||
626 | // read track data | |
627 | for (i = 1; i <= numtracks; i++) { | |
628 | fseek(fi, offset, SEEK_SET); | |
629 | ||
630 | // get the track type | |
631 | ti[i].type = ((fgetc(fi) == 0xA9) ? CDDA : DATA); | |
632 | fseek(fi, 8, SEEK_CUR); | |
633 | ||
634 | // get the track starting point | |
635 | ti[i].start[0] = fgetc(fi); | |
636 | ti[i].start[1] = fgetc(fi); | |
637 | ti[i].start[2] = fgetc(fi); | |
638 | ||
639 | if (i > 1) { | |
640 | l = msf2sec(ti[i].start); | |
641 | sec2msf(l - 2 * 75, ti[i].start); // ??? | |
642 | } | |
643 | ||
644 | // get the track length | |
645 | fread(&extra_offset, 1, sizeof(unsigned int), fi); | |
646 | extra_offset = SWAP32(extra_offset); | |
647 | ||
648 | fseek(fi, extra_offset + 4, SEEK_SET); | |
649 | fread(&l, 1, sizeof(unsigned int), fi); | |
650 | l = SWAP32(l); | |
651 | sec2msf(l, ti[i].length); | |
652 | ||
653 | offset += 0x50; | |
654 | } | |
655 | ||
656 | fclose(fi); | |
657 | return 0; | |
658 | } | |
659 | ||
660 | // this function tries to get the .sub file of the given .img | |
661 | static int opensubfile(const char *isoname) { | |
662 | char subname[MAXPATHLEN]; | |
663 | ||
664 | // copy name of the iso and change extension from .img to .sub | |
665 | strncpy(subname, isoname, sizeof(subname)); | |
666 | subname[MAXPATHLEN - 1] = '\0'; | |
667 | if (strlen(subname) >= 4) { | |
668 | strcpy(subname + strlen(subname) - 4, ".sub"); | |
669 | } | |
670 | else { | |
671 | return -1; | |
672 | } | |
673 | ||
674 | subHandle = fopen(subname, "rb"); | |
675 | if (subHandle == NULL) { | |
676 | return -1; | |
677 | } | |
678 | ||
679 | return 0; | |
680 | } | |
681 | ||
ef79bbde P |
682 | static void PrintTracks(void) { |
683 | int i; | |
684 | ||
685 | for (i = 1; i <= numtracks; i++) { | |
686 | SysPrintf(_("Track %.2d (%s) - Start %.2d:%.2d:%.2d, Length %.2d:%.2d:%.2d\n"), | |
687 | i, (ti[i].type == DATA ? "DATA" : "AUDIO"), | |
688 | ti[i].start[0], ti[i].start[1], ti[i].start[2], | |
689 | ti[i].length[0], ti[i].length[1], ti[i].length[2]); | |
690 | } | |
691 | } | |
692 | ||
693 | // This function is invoked by the front-end when opening an ISO | |
694 | // file for playback | |
695 | static long CALLBACK ISOopen(void) { | |
696 | if (cdHandle != NULL) { | |
697 | return 0; // it's already open | |
698 | } | |
699 | ||
700 | cdHandle = fopen(GetIsoFile(), "rb"); | |
701 | if (cdHandle == NULL) { | |
702 | return -1; | |
703 | } | |
704 | ||
705 | SysPrintf(_("Loaded CD Image: %s"), GetIsoFile()); | |
706 | ||
707 | cddaBigEndian = FALSE; | |
708 | subChanMixed = FALSE; | |
709 | subChanRaw = FALSE; | |
710 | ||
711 | if (parsecue(GetIsoFile()) == 0) { | |
712 | SysPrintf("[+cue]"); | |
713 | } | |
714 | else if (parsetoc(GetIsoFile()) == 0) { | |
715 | SysPrintf("[+toc]"); | |
716 | } | |
717 | else if (parseccd(GetIsoFile()) == 0) { | |
718 | SysPrintf("[+ccd]"); | |
719 | } | |
720 | else if (parsemds(GetIsoFile()) == 0) { | |
721 | SysPrintf("[+mds]"); | |
722 | } | |
723 | ||
724 | if (!subChanMixed && opensubfile(GetIsoFile()) == 0) { | |
725 | SysPrintf("[+sub]"); | |
726 | } | |
727 | ||
728 | SysPrintf(".\n"); | |
729 | ||
730 | PrintTracks(); | |
731 | ||
38b8102c | 732 | // make sure we have another handle open for cdda |
733 | if (numtracks > 1 && ti[1].handle == NULL) { | |
734 | ti[1].handle = fopen(GetIsoFile(), "rb"); | |
735 | } | |
736 | ||
ef79bbde P |
737 | return 0; |
738 | } | |
739 | ||
740 | static long CALLBACK ISOclose(void) { | |
38b8102c | 741 | int i; |
742 | ||
ef79bbde P |
743 | if (cdHandle != NULL) { |
744 | fclose(cdHandle); | |
745 | cdHandle = NULL; | |
746 | } | |
747 | if (subHandle != NULL) { | |
748 | fclose(subHandle); | |
749 | subHandle = NULL; | |
750 | } | |
751 | stopCDDA(); | |
38b8102c | 752 | cddaHandle = NULL; |
753 | ||
754 | for (i = 1; i <= numtracks; i++) { | |
755 | if (ti[i].handle != NULL) { | |
756 | fclose(ti[i].handle); | |
757 | ti[i].handle = NULL; | |
758 | } | |
759 | } | |
760 | numtracks = 0; | |
761 | ||
762 | return 0; | |
763 | } | |
764 | ||
765 | static long CALLBACK ISOinit(void) { | |
766 | assert(cdHandle == NULL); | |
767 | assert(subHandle == NULL); | |
768 | ||
769 | return 0; // do nothing | |
770 | } | |
771 | ||
772 | static long CALLBACK ISOshutdown(void) { | |
773 | ISOclose(); | |
ef79bbde P |
774 | return 0; |
775 | } | |
776 | ||
777 | // return Starting and Ending Track | |
778 | // buffer: | |
779 | // byte 0 - start track | |
780 | // byte 1 - end track | |
781 | static long CALLBACK ISOgetTN(unsigned char *buffer) { | |
782 | buffer[0] = 1; | |
783 | ||
784 | if (numtracks > 0) { | |
785 | buffer[1] = numtracks; | |
786 | } | |
787 | else { | |
788 | buffer[1] = 1; | |
789 | } | |
790 | ||
791 | return 0; | |
792 | } | |
793 | ||
794 | // return Track Time | |
795 | // buffer: | |
796 | // byte 0 - frame | |
797 | // byte 1 - second | |
798 | // byte 2 - minute | |
799 | static long CALLBACK ISOgetTD(unsigned char track, unsigned char *buffer) { | |
800 | if (numtracks > 0 && track <= numtracks) { | |
801 | buffer[2] = ti[track].start[0]; | |
802 | buffer[1] = ti[track].start[1]; | |
803 | buffer[0] = ti[track].start[2]; | |
804 | } | |
805 | else { | |
806 | buffer[2] = 0; | |
807 | buffer[1] = 2; | |
808 | buffer[0] = 0; | |
809 | } | |
810 | ||
811 | return 0; | |
812 | } | |
813 | ||
814 | // decode 'raw' subchannel data ripped by cdrdao | |
815 | static void DecodeRawSubData(void) { | |
816 | unsigned char subQData[12]; | |
817 | int i; | |
818 | ||
819 | memset(subQData, 0, sizeof(subQData)); | |
820 | ||
821 | for (i = 0; i < 8 * 12; i++) { | |
822 | if (subbuffer[i] & (1 << 6)) { // only subchannel Q is needed | |
823 | subQData[i >> 3] |= (1 << (7 - (i & 7))); | |
824 | } | |
825 | } | |
826 | ||
827 | memcpy(&subbuffer[12], subQData, 12); | |
828 | } | |
829 | ||
830 | // read track | |
831 | // time: byte 0 - minute; byte 1 - second; byte 2 - frame | |
832 | // uses bcd format | |
833 | static long CALLBACK ISOreadTrack(unsigned char *time) { | |
834 | if (cdHandle == NULL) { | |
835 | return -1; | |
836 | } | |
837 | ||
838 | if (subChanMixed) { | |
839 | fseek(cdHandle, MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2])) * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE) + 12, SEEK_SET); | |
840 | fread(cdbuffer, 1, DATA_SIZE, cdHandle); | |
841 | fread(subbuffer, 1, SUB_FRAMESIZE, cdHandle); | |
842 | ||
843 | if (subChanRaw) DecodeRawSubData(); | |
844 | } | |
845 | else { | |
846 | fseek(cdHandle, MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2])) * CD_FRAMESIZE_RAW + 12, SEEK_SET); | |
847 | fread(cdbuffer, 1, DATA_SIZE, cdHandle); | |
848 | ||
849 | if (subHandle != NULL) { | |
850 | fseek(subHandle, MSF2SECT(btoi(time[0]), btoi(time[1]), btoi(time[2])) * SUB_FRAMESIZE, SEEK_SET); | |
851 | fread(subbuffer, 1, SUB_FRAMESIZE, subHandle); | |
852 | ||
853 | if (subChanRaw) DecodeRawSubData(); | |
854 | } | |
855 | } | |
856 | ||
857 | return 0; | |
858 | } | |
859 | ||
860 | // return readed track | |
861 | static unsigned char * CALLBACK ISOgetBuffer(void) { | |
862 | return cdbuffer; | |
863 | } | |
864 | ||
865 | // plays cdda audio | |
866 | // sector: byte 0 - minute; byte 1 - second; byte 2 - frame | |
867 | // does NOT uses bcd format | |
868 | static long CALLBACK ISOplay(unsigned char *time) { | |
38b8102c | 869 | unsigned int i, sect; |
870 | ||
7b8da7ab | 871 | if (numtracks <= 1) |
872 | return 0; | |
873 | ||
38b8102c | 874 | // find the track |
875 | sect = msf2sec(time); | |
876 | for (i = numtracks; i > 1; i--) | |
877 | if (msf2sec(ti[i].start) <= sect + 2 * 75) | |
878 | break; | |
879 | ||
880 | // find the file that contains this track | |
881 | for (; i > 1; i--) | |
882 | if (ti[i].handle != NULL) | |
883 | break; | |
884 | ||
885 | cddaStartOffset = msf2sec(ti[i].start); | |
886 | sect -= cddaStartOffset - 2 * 75; | |
887 | cddaHandle = ti[i].handle; | |
888 | ||
ef79bbde P |
889 | if (SPU_playCDDAchannel != NULL) { |
890 | if (subChanMixed) { | |
38b8102c | 891 | startCDDA(sect * (CD_FRAMESIZE_RAW + SUB_FRAMESIZE)); |
ef79bbde P |
892 | } |
893 | else { | |
38b8102c | 894 | startCDDA(sect * CD_FRAMESIZE_RAW); |
ef79bbde P |
895 | } |
896 | } | |
897 | return 0; | |
898 | } | |
899 | ||
900 | // stops cdda audio | |
901 | static long CALLBACK ISOstop(void) { | |
902 | stopCDDA(); | |
903 | return 0; | |
904 | } | |
905 | ||
906 | // gets subchannel data | |
907 | static unsigned char* CALLBACK ISOgetBufferSub(void) { | |
908 | if (subHandle != NULL || subChanMixed) { | |
909 | return subbuffer; | |
910 | } | |
911 | ||
912 | return NULL; | |
913 | } | |
914 | ||
915 | static long CALLBACK ISOgetStatus(struct CdrStat *stat) { | |
916 | int sec; | |
917 | ||
918 | CDR__getStatus(stat); | |
919 | ||
920 | if (playing) { | |
921 | stat->Type = 0x02; | |
922 | stat->Status |= 0x80; | |
38b8102c | 923 | sec = (cddaStartOffset + cddaCurOffset) / CD_FRAMESIZE_RAW; |
ef79bbde P |
924 | sec2msf(sec, (char *)stat->Time); |
925 | } | |
926 | else { | |
927 | stat->Type = 0x01; | |
928 | } | |
929 | ||
930 | return 0; | |
931 | } | |
932 | ||
933 | void cdrIsoInit(void) { | |
934 | CDR_init = ISOinit; | |
935 | CDR_shutdown = ISOshutdown; | |
936 | CDR_open = ISOopen; | |
937 | CDR_close = ISOclose; | |
938 | CDR_getTN = ISOgetTN; | |
939 | CDR_getTD = ISOgetTD; | |
940 | CDR_readTrack = ISOreadTrack; | |
941 | CDR_getBuffer = ISOgetBuffer; | |
942 | CDR_play = ISOplay; | |
943 | CDR_stop = ISOstop; | |
944 | CDR_getBufferSub = ISOgetBufferSub; | |
945 | CDR_getStatus = ISOgetStatus; | |
946 | ||
947 | CDR_getDriveLetter = CDR__getDriveLetter; | |
948 | CDR_configure = CDR__configure; | |
949 | CDR_test = CDR__test; | |
950 | CDR_about = CDR__about; | |
951 | CDR_setfilename = CDR__setfilename; | |
952 | ||
953 | numtracks = 0; | |
954 | } | |
955 | ||
956 | int cdrIsoActive(void) { | |
957 | return (cdHandle != NULL); | |
958 | } |