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