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