1 /*
2  * AudioCD/VideoCD BinCue
3  *
4  * This file is part of MPlayer.
5  *
6  * MPlayer is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * MPlayer is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License along
17  * with MPlayer; if not, write to the Free Software Foundation, Inc.,
18  * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
19  */
20 
21 #include <stdio.h>
22 #include <stdlib.h>
23 #include <string.h>
24 
25 #include <sys/types.h>
26 #include <sys/stat.h>
27 #include <unistd.h>
28 #include <fcntl.h>
29 
30 #include "config.h"
31 #include "mp_msg.h"
32 #include "help_mp.h"
33 
34 #include "stream.h"
35 
36 #include "help_mp.h"
37 #include "m_option.h"
38 #include "m_struct.h"
39 #include "libavutil/avstring.h"
40 
41 #define SIZERAW 2352
42 #define SIZEISO_MODE1 2048
43 #define SIZEISO_MODE2_RAW 2352
44 #define SIZEISO_MODE2_FORM1 2048
45 #define SIZEISO_MODE2_FORM2 2336
46 #define AUDIO 0
47 #define MODE1 1
48 #define MODE2 2
49 #define MODE1_2352 10
50 #define MODE2_2352 20
51 #define MODE1_2048 30
52 #define MODE2_2336 40
53 #define UNKNOWN -1
54 
55 static const struct stream_priv_s {
56   char* filename;
57 } stream_priv_dflts = {
58   NULL
59 };
60 
61 #define ST_OFF(f) M_ST_OFF(struct stream_priv_s,f)
62 /// URL definition
63 static const m_option_t stream_opts_fields[] = {
64   { "string", ST_OFF(filename), CONF_TYPE_STRING, 0, 0 ,0, NULL},
65   { NULL, NULL, 0, 0, 0, 0,  NULL }
66 };
67 static const struct m_struct_st stream_opts = {
68   "cue",
69   sizeof(struct stream_priv_s),
70   &stream_priv_dflts,
71   stream_opts_fields
72 };
73 
74 static char cue_filename[256];
75 static char bincue_path[256];
76 
77 
78 typedef struct track
79 {
80    unsigned short mode;
81    unsigned short minute;
82    unsigned short second;
83    unsigned short frame;
84 
85    /* (min*60 + sec) * 75 + fps   */
86 
87    unsigned long start_sector;
88 
89    /* = the sizes in bytes off all tracks bevor this one */
90    /* its needed if there are mode1 tracks befor the mpeg tracks */
91    unsigned long start_offset;
92 
93    unsigned int sector_data_length;
94    /*   unsigned char num[3]; */
95 } tTrack;
96 
97 /* max 99 tracks on a cd */
98 static tTrack tracks[100];
99 
100 static struct cue_track_pos {
101   int track;
102   unsigned short mode;
103   unsigned short minute;
104   unsigned short second;
105   unsigned short frame;
106 } cue_current_pos;
107 
108 /* number of tracks on the cd */
109 static int nTracks = 0;
110 
digits2int(const char s[2],int errval)111 static int digits2int(const char s[2], int errval) {
112   uint8_t a = s[0] - '0';
113   uint8_t b = s[1] - '0';
114   if (a > 9 || b > 9)
115     return errval;
116   return a * 10 + b;
117 }
118 
119 /* presumes Line is preloaded with the "current" line of the file */
cue_getTrackinfo(FILE * fd_cue,char * Line,tTrack * track)120 static int cue_getTrackinfo(FILE *fd_cue, char *Line, tTrack *track)
121 {
122   int already_set = 0;
123 
124   /* Get the 'mode' */
125   if (strncmp(&Line[2], "TRACK ", 6)==0)
126   {
127 /*    strncpy(track->num, &Line[8], 2); track->num[2] = '\0'; */
128 
129     track->mode = UNKNOWN;
130     if(strncmp(&Line[11], "AUDIO", 5)==0) track->mode = AUDIO;
131     if(strncmp(&Line[11], "MODE1/2352", 10)==0) track->mode = MODE1_2352;
132     if(strncmp(&Line[11], "MODE1/2048", 10)==0) track->mode = MODE1_2048;
133     if(strncmp(&Line[11], "MODE2/2352", 10)==0) track->mode = MODE2_2352;
134     if(strncmp(&Line[11], "MODE2/2336", 10)==0) track->mode = MODE2_2336;
135     track->sector_data_length = track->mode == AUDIO ? SIZERAW : VCD_SECTOR_DATA;
136   }
137   else return 1;
138 
139   /* Get the track indexes */
140   while(1) {
141     if(! fgets( Line, 256, fd_cue ) ) { break;}
142 
143     if (strncmp(&Line[2], "TRACK ", 6)==0)
144     {
145       /* next track starting */
146       break;
147     }
148 
149     /* Track 0 or 1, take the first an get fill the values*/
150     if (strncmp(&Line[4], "INDEX ", 6)==0)
151     {
152       /* check stuff here so if the answer is false the else stuff below won't be executed */
153       if ((already_set == 0) && digits2int(Line + 10, 100) <= 1)
154       {
155         already_set = 1;
156 
157         track->minute = digits2int(Line + 13, 0);
158         track->second = digits2int(Line + 16, 0);
159         track->frame  = digits2int(Line + 19, 0);
160       }
161     }
162     else if (strncmp(&Line[4], "PREGAP ", 7)==0) { ; /* ignore */ }
163     else if (strncmp(&Line[4], "FLAGS ", 6)==0)  { ; /* ignore */ }
164     else mp_msg (MSGT_OPEN,MSGL_INFO,
165                  MSGTR_MPDEMUX_CUEREAD_UnexpectedCuefileLine, Line);
166   }
167   return 0;
168 }
169 
170 
171 
172 /* FIXME: the string operations ( strcpy,strcat ) below depend
173  * on the arrays to have the same size, thus we need to make
174  * sure the sizes are in sync.
175  */
cue_find_bin(const char * firstline)176 static int cue_find_bin (const char *firstline) {
177   struct stat filestat;
178   const char *cur_name;
179   char bin_filename[256];
180   char s[256];
181   char t[256];
182   int fd_bin;
183   int i = 0;
184 
185   /* get the filename out of that */
186   /*                      12345 6  */
187   mp_msg (MSGT_OPEN,MSGL_INFO, MSGTR_MPDEMUX_CUEREAD_BinFilenameFound, firstline);
188   if (strncmp(firstline, "FILE \"",6)==0)
189   {
190     firstline += 6;
191     while ( *firstline && *firstline != '"')
192     {
193       bin_filename[i] = *firstline++;
194 
195       /* if I found a path info, then delete all before it */
196       switch (bin_filename[i])
197       {
198         case '\\':
199           i = 0;
200           break;
201 
202         case '/':
203           i = 0;
204           break;
205 
206         default:
207           i++;
208       }
209     }
210   }
211   bin_filename[i] = '\0';
212 
213   fd_bin = -1;
214   for (i = 0; fd_bin == -1 && i < 6; i++) {
215     if (i <=1 && bin_filename[0] == '\0')
216       continue;
217     if (i > 1 && strlen(cue_filename) < 3)
218       break;
219 
220     switch (i) {
221     case 0:
222       /* now try to open that file, without path */
223       cur_name = bin_filename;
224       break;
225     case 1:
226       /* now try to find it with the path of the cue file */
227       snprintf(s,sizeof( s ),"%s/%s",bincue_path,bin_filename);
228       cur_name = s;
229       break;
230     case 2:
231       /* now I would say the whole filename is shit, build our own */
232       av_strlcpy(s, cue_filename, strlen(cue_filename) - 3 );
233       strcat(s, ".bin");
234       cur_name = s;
235       break;
236     case 3:
237       /* ok try it with path */
238       snprintf(t, sizeof( t ), "%s/%s", bincue_path, s);
239       cur_name = t;
240       break;
241     case 4:
242       /* now I would say the whole filename is shit, build our own */
243       av_strlcpy(s, cue_filename, strlen(cue_filename) - 3 );
244       strcat(s, ".img");
245       cur_name = s;
246       break;
247     case 5:
248       /* ok try it with path */
249       snprintf(t, sizeof( t ), "%s/%s", bincue_path, s);
250       cur_name = t;
251       break;
252     }
253     fd_bin = open(cur_name, O_RDONLY);
254     if (fd_bin != -1 && (fstat(fd_bin, &filestat) == -1 || !S_ISREG(filestat.st_mode))) {
255         close(fd_bin);
256         fd_bin = -1;
257     }
258     if (fd_bin == -1) {
259       mp_msg(MSGT_OPEN,MSGL_INFO, MSGTR_MPDEMUX_CUEREAD_BinFilenameTested,
260             cur_name);
261     }
262   }
263 
264   if (fd_bin == -1)
265   {
266     /* I'll give up */
267     mp_msg(MSGT_OPEN,MSGL_ERR,
268            MSGTR_MPDEMUX_CUEREAD_CannotFindBinFile);
269     return -1;
270   }
271 
272   mp_msg(MSGT_OPEN,MSGL_INFO,
273          MSGTR_MPDEMUX_CUEREAD_UsingBinFile, cur_name);
274   return fd_bin;
275 }
276 
cue_get_first_track(int fd_bin)277 static int cue_get_first_track(int fd_bin) {
278   char vol_descriptor[5];
279 
280   if (lseek(fd_bin, 16 * VCD_SECTOR_SIZE + VCD_SECTOR_OFFS + 1, SEEK_SET) != -1)
281     if (read(fd_bin, vol_descriptor, 5) == 5)
282       if (strncmp(vol_descriptor, "CD001", 5) == 0)
283         /* ISO 9660 filesystem, so data starting in track 2 */
284         return 2;
285 
286   /* data starting in track 1 */
287   return 1;
288 }
289 
cue_msf_2_sector(int minute,int second,int frame)290 static inline int cue_msf_2_sector(int minute, int second, int frame) {
291  return frame + (second + minute * 60 ) * 75;
292 }
293 
cue_get_msf(void)294 static inline int cue_get_msf(void) {
295   return cue_msf_2_sector (cue_current_pos.minute,
296                            cue_current_pos.second,
297                            cue_current_pos.frame);
298 }
299 
cue_set_msf(unsigned int sect)300 static inline void cue_set_msf(unsigned int sect){
301   cue_current_pos.frame=sect%75;
302   sect=sect/75;
303   cue_current_pos.second=sect%60;
304   sect=sect/60;
305   cue_current_pos.minute=sect;
306 }
307 
cue_mode_2_sector_size(int mode)308 static inline int cue_mode_2_sector_size(int mode)
309 {
310   switch (mode)
311   {
312     case AUDIO:      return SIZERAW;
313     case MODE1_2352: return SIZERAW;
314     case MODE1_2048: return SIZEISO_MODE1;
315     case MODE2_2352: return SIZEISO_MODE2_RAW;
316     case MODE2_2336: return SIZEISO_MODE2_FORM2;
317 
318     default:
319       mp_msg(MSGT_OPEN,MSGL_FATAL,
320              MSGTR_MPDEMUX_CUEREAD_UnknownModeForBinfile);
321       abort();
322   }
323 
324 }
325 
326 
cue_read_cue(const char * in_cue_filename)327 static int cue_read_cue (const char *in_cue_filename)
328 {
329   struct stat filestat;
330   char sLine[256];
331   unsigned int sect;
332   char *s,*t;
333   int i;
334   int fd_bin;
335   FILE *fd_cue;
336 
337   /* we have no tracks at the beginning */
338   nTracks = 0;
339 
340   /* split the filename into a path and filename part */
341   s = strdup(in_cue_filename);
342   t = strrchr(s, '/');
343   if (!t)
344      t = ".";
345   else {
346      *t = '\0';
347      t = s;
348      if (*t == '\0')
349        strcpy(t, "/");
350   }
351 
352   av_strlcpy(bincue_path,t,sizeof( bincue_path ));
353   mp_msg(MSGT_OPEN,MSGL_V,"dirname: %s, cuepath: %s\n", t, bincue_path);
354   free(s);
355   s = t = NULL;
356 
357   /* no path at all? */
358   if (strcmp(bincue_path, ".") == 0) {
359     mp_msg(MSGT_OPEN,MSGL_V,"bincue_path: %s\n", bincue_path);
360     av_strlcpy(cue_filename,in_cue_filename,sizeof( cue_filename ));
361   } else {
362     av_strlcpy(cue_filename,in_cue_filename + strlen(bincue_path) + 1,
363             sizeof( cue_filename ));
364   }
365 
366 
367 
368   /* open the cue file */
369   fd_cue = fopen (in_cue_filename, "r");
370   if (fd_cue == NULL)
371   {
372     mp_msg(MSGT_OPEN,MSGL_ERR,
373            MSGTR_MPDEMUX_CUEREAD_CannotOpenCueFile, in_cue_filename);
374     return -1;
375   }
376 
377   /* read the first line and hand it to find_bin, which will
378      test more than one possible name of the file */
379 
380   if(! fgets( sLine, sizeof(sLine), fd_cue ) )
381   {
382     mp_msg(MSGT_OPEN,MSGL_ERR,
383            MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
384     fclose (fd_cue);
385     return -1;
386   }
387 
388   fd_bin = cue_find_bin(sLine);
389   if (fd_bin == -1) {
390     fclose (fd_cue);
391     return -1;
392   }
393 
394 
395   /* now build the track list */
396   /* red the next line and call our track finder */
397   if(! fgets( sLine, sizeof(sLine), fd_cue ) )
398   {
399     mp_msg(MSGT_OPEN,MSGL_ERR,
400            MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
401     fclose (fd_cue);
402     close (fd_bin);
403     return -1;
404   }
405 
406   while(!feof(fd_cue))
407   {
408     if (cue_getTrackinfo(fd_cue, sLine, &tracks[nTracks++]) != 0)
409     {
410       mp_msg(MSGT_OPEN,MSGL_ERR,
411              MSGTR_MPDEMUX_CUEREAD_ErrReadingFromCueFile, in_cue_filename);
412       fclose (fd_cue);
413       close (fd_bin);
414       return -1;
415     }
416   }
417 
418   /* make a fake track with stands for the Lead out */
419   if (fstat (fd_bin, &filestat) == -1) {
420     mp_msg(MSGT_OPEN,MSGL_ERR,
421            MSGTR_MPDEMUX_CUEREAD_ErrGettingBinFileSize);
422     fclose (fd_cue);
423     close (fd_bin);
424     return -1;
425   }
426 
427   sect = filestat.st_size / 2352;
428 
429   tracks[nTracks].frame = sect%75;
430   sect=sect/75;
431   tracks[nTracks].second = sect%60;
432   sect=sect/60;
433   tracks[nTracks].minute = sect;
434 
435 
436   /* let's calculate the start sectors and offsets */
437   for(i = 0; i <= nTracks; i++)
438   {
439     tracks[i].start_sector = cue_msf_2_sector(tracks[i].minute,
440                                               tracks[i].second,
441                                               tracks[i].frame);
442 
443     /* if we're the first track we don't need to offset of the one befor */
444     if (i == 0)
445     {
446       /* was always 0 on my svcds, but who knows */
447       tracks[0].start_offset = tracks[0].start_sector *
448         cue_mode_2_sector_size(tracks[0].mode);
449     } else
450     {
451       tracks[i].start_offset = tracks[i-1].start_offset +
452         (tracks[i].start_sector - tracks[i-1].start_sector) *
453         cue_mode_2_sector_size(tracks[i-1].mode);
454     }
455   }
456 
457   fclose (fd_cue);
458 
459   return fd_bin;
460 }
461 
462 
463 
464 
cue_read_toc_entry(int track)465 static int cue_read_toc_entry(int track) {
466   /* check if its a valid track, if not return -1 */
467   if (track <= 0 || track > nTracks)
468     return -1;
469 
470 
471   cue_current_pos.track = track;
472   track--;
473   switch (tracks[track].mode)
474   {
475     case AUDIO:
476       cue_current_pos.mode = AUDIO;
477       break;
478     case MODE1_2352:
479       cue_current_pos.mode = MODE1;
480       break;
481     case MODE1_2048:
482       cue_current_pos.mode = MODE1;
483       break;
484     default: /* MODE2_2352 and MODE2_2336 */
485       cue_current_pos.mode = MODE2;
486   }
487   cue_current_pos.minute = tracks[track].minute;
488   cue_current_pos.second = tracks[track].second;
489   cue_current_pos.frame = tracks[track].frame;
490 
491   return 0;
492 }
493 
cue_get_track_end(int track)494 static int cue_get_track_end (int track){
495   int sector = cue_msf_2_sector(tracks[track].minute, tracks[track].second,
496                                 tracks[track].frame);
497 
498   return tracks[track-1].sector_data_length * sector;
499 }
500 
seek(stream_t * s,int64_t newpos)501 static int seek(stream_t *s, int64_t newpos) {
502   s->pos=newpos;
503   cue_set_msf(s->pos/tracks[cue_current_pos.track-1].sector_data_length);
504   return 1;
505 }
506 
cue_seek_to_track(stream_t * stream,int track)507 static int cue_seek_to_track (stream_t *stream, int track){
508   int pos;
509   if (cue_read_toc_entry (track))
510     return -1;
511 
512   pos = tracks[track-1].sector_data_length * cue_get_msf();
513   stream->start_pos = pos;
514   stream->end_pos = cue_get_track_end(track);
515   seek(stream, pos);
516   return pos;
517 }
518 
cue_read_toc(void)519 static void cue_read_toc(void){
520   int i;
521   for (i = 0; i < nTracks; ++i) {
522 
523     mp_msg(MSGT_OPEN,MSGL_INFO,
524            MSGTR_MPDEMUX_CUEREAD_InfoTrackFormat,
525            i+1,
526            tracks[i].mode,
527            tracks[i].minute,
528            tracks[i].second,
529            tracks[i].frame
530            );
531   }
532 }
533 
cue_read(stream_t * stream,char * mem,int size)534 static int cue_read(stream_t *stream, char *mem, int size) {
535   unsigned long position, offset;
536   int fd_bin = stream->fd;
537   int track = cue_current_pos.track - 1;
538 
539   position = tracks[track].start_offset +
540              (cue_msf_2_sector(cue_current_pos.minute,
541                                cue_current_pos.second,
542                                cue_current_pos.frame) -
543               tracks[track].start_sector)
544              * cue_mode_2_sector_size(tracks[track].mode);
545 
546 
547   if(position >= tracks[track+1].start_offset)
548     return 0;
549 
550   offset = tracks[track].mode == AUDIO ? 0 : VCD_SECTOR_OFFS;
551 
552   if(lseek(fd_bin, position+offset, SEEK_SET) == -1) {
553     mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_MPDEMUX_CUEREAD_UnexpectedBinFileEOF);
554     return 0;
555   }
556 
557   if(read(fd_bin, mem, tracks[track].sector_data_length) != tracks[track].sector_data_length) {
558     mp_msg(MSGT_OPEN,MSGL_ERR, MSGTR_MPDEMUX_CUEREAD_CannotReadNBytesOfPayload, tracks[track].sector_data_length);
559     return 0;
560   }
561 
562   cue_current_pos.frame++;
563   if (cue_current_pos.frame==75){
564     cue_current_pos.frame=0;
565     cue_current_pos.second++;
566     if (cue_current_pos.second==60){
567       cue_current_pos.second=0;
568       cue_current_pos.minute++;
569     }
570   }
571 
572   return tracks[track].sector_data_length;
573 }
574 
control(stream_t * stream,int cmd,void * arg)575 static int control(stream_t *stream, int cmd, void *arg) {
576   switch(cmd) {
577     case STREAM_CTRL_GET_NUM_TITLES:
578     case STREAM_CTRL_GET_NUM_CHAPTERS:
579     {
580       *(unsigned int *)arg = nTracks;
581       return STREAM_OK;
582     }
583     case STREAM_CTRL_SEEK_TO_CHAPTER:
584     {
585       int r;
586       unsigned int track = *(unsigned int *)arg + 1;
587       r = cue_seek_to_track(stream, track);
588       if (r >= 0) {
589         return STREAM_OK;
590       }
591       break;
592     }
593     case STREAM_CTRL_GET_CURRENT_TITLE:
594     case STREAM_CTRL_GET_CURRENT_CHAPTER:
595     {
596       *(unsigned int *)arg = cue_current_pos.track - 1;
597       return STREAM_OK;
598     }
599     case STREAM_CTRL_GET_NUM_ANGLES:
600     {
601       *(unsigned int *)arg = tracks[cue_current_pos.track - 1].mode != AUDIO;
602       return STREAM_OK;
603     }
604   }
605   return STREAM_UNSUPPORTED;
606 }
607 
open_s(stream_t * stream,int mode,void * opts,int * file_format)608 static int open_s(stream_t *stream,int mode, void* opts, int* file_format) {
609   struct stream_priv_s* p = (struct stream_priv_s*)opts;
610   int ret,f = -1,track = 0;
611   char *filename = NULL, *colon = NULL;
612 
613   if(mode != STREAM_READ || !p->filename)
614     goto err_out;
615   filename = strdup(p->filename);
616   if(!filename)
617     goto err_out;
618   colon = strstr(filename, ":");
619   if(colon) {
620     if(strlen(colon)>1)
621       track = atoi(colon+1);
622     *colon = 0;
623   }
624   f = cue_read_cue(filename);
625   if(f < 0)
626     goto err_out;
627   if(!track)
628     track = cue_get_first_track(f);
629 
630   cue_read_toc();
631   ret=cue_seek_to_track(stream, track);
632   if(ret<0){
633     mp_msg(MSGT_OPEN,MSGL_ERR,MSGTR_ErrTrackSelect " (seek)\n");
634     goto err_out;
635   }
636   mp_msg(MSGT_OPEN,MSGL_INFO,MSGTR_MPDEMUX_CUEREAD_CueStreamInfo_FilenameTrackTracksavail,
637          filename, track, ret, (int)stream->end_pos);
638 
639   stream->fd = f;
640   stream->type = STREAMTYPE_BINCUE;
641   stream->sector_size = tracks[track-1].sector_data_length;
642   stream->flags = STREAM_READ | MP_STREAM_SEEK_FW;
643   stream->fill_buffer = cue_read;
644   stream->seek = seek;
645   stream->control = control;
646 
647   free(filename);
648   m_struct_free(&stream_opts,opts);
649   return STREAM_OK;
650 
651 err_out:
652   if (f >= 0) close(f);
653   free(filename);
654   m_struct_free(&stream_opts,opts);
655   return STREAM_UNSUPPORTED;
656 }
657 
658 const stream_info_t stream_info_cue = {
659   "CUE track",
660   "cue",
661   "Albeu",
662   "based on the code from ???",
663   open_s,
664   { "cue", NULL },
665   &stream_opts,
666   1 // Urls are an option string
667 };
668