gitchangelog.sh: state inside comments that GNU sed is required
[cdimgtools:cdimgtools.git] / dvdimgdecss.c
1 /* dvdimgdecss.c - CSS descrambling of DVD Video images using libdvdcss/libdvdread
2  * Copyrignt © 2012 Géraud Meyer <graud@gmx.com>
3  *   This program is free software; you can redistribute it and/or modify
4  *   it under the terms of the GNU General Public License version 2 as
5  *   published by the Free Software Foundation.
6  *
7  *   This program is distributed in the hope that it will be useful, but
8  *   WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
9  *   or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
10  *   for more details.
11  *
12  *   You should have received a copy of the GNU General Public License along
13  *   with this program.  If not, see <http://www.gnu.org/licenses/>.
14  */
15
16 #if HAVE_CONFIG_H
17 #   include "config.h"
18 #endif
19 #include <stdlib.h>
20 #include <stdarg.h>
21 #include <stdio.h>
22 #include <getopt.h>
23 #include <errno.h>
24 #include <string.h>
25 #include <sys/types.h>
26 #include <unistd.h>
27 #include <sys/stat.h>
28 #include <fcntl.h>
29 #include <dvdread/dvd_reader.h>
30 #include <dvdread/dvd_udf.h>
31 #include <dvdcss/dvdcss.h>
32
33 #define EX_SUCCESS 0
34 #define EX_USAGE (~((~0)<<8))
35 #define EX_OPEN (~((~0)<<7))
36 #define EX_IO (1<<6)
37 #define EX_MISMATCH (1<<5)
38 #define EX_MEM (1<<4)
39 #define EX_NOP (1<<3)
40 #ifndef PROGRAM_NAME
41 #       define PROGRAM_NAME "dvdimgdecss"
42 #endif
43 #ifndef PROGRAM_VERSION
44 #       define PROGRAM_VERSION "0.1"
45 #endif
46 const char *progname = PROGRAM_NAME;
47 const char *progversion = PROGRAM_VERSION;
48 char verbosity = 1;
49 char dvdread_check = 0;
50 char dvdread_decrypt = 0;
51 #define TITLE_MAX 100
52
53 /* Make an array of an enum so as to iterate */
54 #define DOMAIN_MAX 4
55 const dvd_read_domain_t dvd_read_domains[DOMAIN_MAX] = {
56         DVD_READ_INFO_FILE,
57         DVD_READ_MENU_VOBS,
58         DVD_READ_TITLE_VOBS,
59         DVD_READ_INFO_BACKUP_FILE,
60 };
61
62 static const char *domainname( dvd_read_domain_t domain )
63 {
64         switch( domain ) {
65         case DVD_READ_INFO_FILE:
66                 return "INFO";
67         case DVD_READ_MENU_VOBS:
68                 return "MENU";
69         case DVD_READ_TITLE_VOBS:
70                 return "VOBS";
71         case DVD_READ_INFO_BACKUP_FILE:
72                 return "IBUP";
73         default:
74                 return "Unknown";
75         }
76 }
77
78 /* A negative size means inexistent; a zero size means empty */
79 typedef struct {
80         int start;
81         int size;
82 } block_t;
83
84 typedef struct {
85         block_t ifo, menu, vob, bup;
86 } titleblocks_t;
87
88 block_t *domainblock( titleblocks_t *tblocks, dvd_read_domain_t domain )
89 {
90         if( ! tblocks ) return NULL;
91         switch( domain ) {
92         case DVD_READ_INFO_FILE:
93                 return &tblocks->ifo;
94         case DVD_READ_MENU_VOBS:
95                 return &tblocks->menu;
96         case DVD_READ_TITLE_VOBS:
97                 return &tblocks->vob;
98         case DVD_READ_INFO_BACKUP_FILE:
99                 return &tblocks->bup;
100         default:
101                 return NULL;
102         }
103 }
104
105 typedef struct blockl {
106         block_t  block;
107         struct blockl *tail;
108 } *blockl_t;
109
110 static void usage( )
111 {
112         printf( "Usage:\n" );
113         printf( "\t%s -V\n", progname );
114         printf( "\t%s [-v|-q] [-c] <dvd>\n", progname );
115         printf( "\t%s [-v|-q] [-c|-C] <dvd> <out_file>\n", progname );
116 }
117
118 static int  dvdsize        ( const char * );
119 static int  savetitleblocks( dvd_reader_t *, titleblocks_t (*)[TITLE_MAX] );
120 static int  fileblock      ( dvd_reader_t *, char *, block_t * );
121 static int  removetitles   ( blockl_t, titleblocks_t [] );
122 static int  removeblock    ( blockl_t, const block_t );
123 static int  decrypttitles  ( dvd_reader_t *, dvdcss_t, int, titleblocks_t [] );
124 static int  copyblocks     ( dvdcss_t, int, blockl_t );
125 static int  copyblock      ( dvd_file_t *, dvdcss_t, int, block_t, const char * );
126 dvd_file_t *openfile       ( dvd_reader_t *, int, dvd_read_domain_t );
127 static int  progress       ( const int );
128 static int  printe         ( const char, const char *, ... );
129
130 /* Main for a command line tool */
131 int main( int argc, char *argv[] )
132 {
133         char          *dvdfile, *imgfile = NULL;
134         dvd_reader_t  *dvd;
135         dvdcss_t      dvdcss = NULL;
136         int           img;
137         titleblocks_t titles[TITLE_MAX];
138         struct blockl blocks;
139         int           rc, status = EX_SUCCESS;
140
141         setvbuf( stdout, NULL, _IOLBF, BUFSIZ );
142
143         /* Options */
144         extern int optind;
145         while( (rc = getopt( argc, argv, "qvcCV" )) != -1 )
146                 switch( (char)rc ) {
147                 case 'q':
148                         verbosity--;
149                         break;
150                 case 'v':
151                         verbosity++;
152                 case 'c':
153                         dvdread_check = 1;
154                         break;
155                 case 'C':
156                         dvdread_check = 1;
157                         dvdread_decrypt = 1;
158                         break;
159                 case 'V':
160                         printf( "%s version %s (libdvdcss version %s)\n",
161                           progname, progversion, dvdcss_interface_2 );
162                         exit( EX_SUCCESS );
163                         break;
164                 case '?':
165                 default:
166                         usage( );
167                         exit( EX_USAGE );
168                 }
169         argc -= optind;
170         argv += optind;
171
172         /* Command line args */
173         if( argc < 1 || argc > 2 ) {
174                 printe( 1, "syntax error\n" );
175                 usage( );
176                 exit( EX_USAGE );
177         }
178         dvdfile = argv[0];
179         if( argc == 2 ) imgfile = argv[1];
180         if( !imgfile ) verbosity++;
181
182         /* Open the DVD */
183         printe( 2, "%s: version %s (libdvdcss version %s)\n",
184           progname, progversion, dvdcss_interface_2 );
185         dvdcss = dvdcss_open( dvdfile );
186         if( dvdcss == NULL ) {
187                 printe( 1, "opening of the DVD (%s) with libdvdcss failed\n", dvdfile );
188                 exit( status | EX_OPEN );
189         }
190         dvd = DVDOpen( dvdfile );
191         if( dvd == NULL ) {
192                 printe( 1, "opening of the  DVD (%s) failed\n", dvdfile );
193                 exit( status | EX_OPEN );
194         }
195
196         /* Search the DVD for the positions of the title files */
197         blocks.tail = NULL;
198         blocks.block.start = 0;
199         blocks.block.size = dvdsize( dvdfile );
200         printe( 3, "%s: DVD end at 0x%08x\n", progname, blocks.block.size );
201         status |= savetitleblocks( dvd, &titles );
202         if( blocks.block.size < 0 ) {
203                 printe( 1, "cannot determine the size of the DVD\n" );
204                 blocks.block.size = 0;
205         }
206         else
207                 status |= removetitles( &blocks, titles );
208
209         /* Make libdvdread try to get all the title keys now */
210         if( dvdread_check ) openfile( dvd, 0, DVD_READ_MENU_VOBS );
211
212         /* Check & Decrypt & Write */
213         if( imgfile ) {
214                 img = open( imgfile, O_RDWR | O_CREAT,
215                   S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH );
216                 if( img < 0 ) {
217                         printe( 1, "opening of the image file (%s) failed (%s)\n",
218                           imgfile, strerror( errno ) );
219                         status |= EX_OPEN;
220                 }
221                 else {
222                         printe( 3, "\n" );
223                         status |= decrypttitles( dvd, dvdcss, img, titles );
224                         status |= copyblocks( dvdcss, img, &blocks );
225                         if( close( img ) < 0 ) {
226                                 printe( 1, "closing of the image file failed (%s)\n",
227                                   strerror( errno ) );
228                                 status |= EX_IO;
229                         }
230                 }
231         }
232
233         /* Close DVD */
234         DVDClose( dvd );
235         if( dvdcss_close( dvdcss ) < 0 ) {
236                 printe( 1, "closing of the DVD with libdvdcss failed\n" );
237                 status |= EX_IO;
238         }
239         exit( status );
240 }
241
242 /* Return the size in sectors (whether it is a file or a special device) */
243 static int dvdsize( const char *dvdfile )
244 {
245         off_t       size;
246         struct stat buf;
247         int         dvd, rc;
248
249         rc = stat( dvdfile, &buf );
250         if( rc < 0 ) {
251                 printe( 1, "stat DVD (%s) failed (%s)\n", dvdfile, strerror( errno ) );
252                 return -1;
253         }
254
255         if( !buf.st_rdev )
256                 size = buf.st_size;
257         else {
258                 dvd = open( dvdfile, O_RDONLY );
259                 if( dvd < 0 ) {
260                         printe( 1, "opening the DVD (%s) failed (%s)\n",
261                           dvdfile, strerror( errno ) );
262                         return -1;
263                 }
264                 size = lseek( dvd, 0, SEEK_END );
265                 if( size < 0 ) {
266                         printe( 1, "seeking at the end of the DVD failed (%s)\n",
267                           strerror( errno ) );
268                         return -1;
269                 }
270                 if( close( dvd ) < 0 )
271                         printe( 1, "closing of the DVD failed (%s)\n", strerror( errno ) );
272         }
273
274         if( size % DVD_VIDEO_LB_LEN )
275                 printe( 1, "DVD size is not a block multiple\n" );
276         return size / DVD_VIDEO_LB_LEN;
277 }
278
279 /* Save the sector positions of the title/domain files */
280 static int savetitleblocks( dvd_reader_t *dvd, titleblocks_t (*titles)[TITLE_MAX] )
281 {
282         int           status = EX_SUCCESS;
283         char          filename[32]; /* MAX_UDF_FILE_NAME_LEN too much */
284         titleblocks_t *tblocks;
285         block_t       block;
286         int           count = 0, title, i, start;
287
288         /* Video Manager */
289         title = 0;
290         tblocks = &(*titles)[title];
291         sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "IFO" );
292         fileblock( dvd, filename, &tblocks->ifo )
293         || printf( "%s: WARNING %s not found\n", progname, filename );
294         sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "VOB" );
295         fileblock( dvd, filename, &tblocks->menu );
296         fileblock( dvd, "/VIDEO_TS/invalid_name/", &tblocks->vob );
297         sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "BUP" );
298         fileblock( dvd, filename, &tblocks->bup );
299
300         /* Titles */
301         for( title = 1; title < TITLE_MAX; title++ ) {
302                 tblocks = &(*titles)[title];
303                 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "IFO" );
304                 fileblock( dvd, filename, &tblocks->ifo )
305                 && count++;
306                 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "VOB" );
307                 fileblock( dvd, filename, &tblocks->menu );
308                 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 1, "VOB" );
309                 fileblock( dvd, filename, &tblocks->vob );
310                 for( i = 2, start = tblocks->vob.start+tblocks->vob.size; i < 10; i++ ) {
311                         /* Title VOBs may be split into several files */
312                         sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, i, "VOB" );
313                         if( fileblock( dvd, filename, &block ) ) {
314                                 tblocks->vob.size += block.size;
315                                 if( block.start != start ) {
316                                         printe( 1, "WARNING whole in title %d before part %d\n",
317                                           title, i );
318                                         status |= EX_MISMATCH;
319                                 }
320                                 start = block.start+block.size;
321                         }
322                 }
323                 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "BUP" );
324                 fileblock( dvd, filename, &tblocks->bup );
325         }
326
327         printe( 3, "%s: %d titles found\n", progname, count );
328         return status;
329 }
330
331 /* Record the sector range over which a file spans */
332 static int fileblock( dvd_reader_t *dvd, char *filename, block_t *block )
333 {
334         uint32_t sector, size;
335         (*block).start = 0;
336         (*block).size = -1;
337
338         sector = UDFFindFile( dvd, filename, &size );
339         if( sector ) {
340                 if( size % DVD_VIDEO_LB_LEN )
341                         printe( 1, "WARNING size of %s is not a block multiple\n",
342                           filename );
343                 size /= DVD_VIDEO_LB_LEN;
344                 (*block).start = sector;
345                 (*block).size = size;
346                 printe( 3, "%s: %s at 0x%08x-0x%08x\n",
347                   progname, filename, sector, sector+size );
348                 return 1;
349         }
350
351         return 0;
352 }
353
354 /* Remove from blocks the block sectors of all title/domains */
355 static int removetitles( blockl_t blocks, titleblocks_t titles[] )
356 {
357         block_t           *block;
358         dvd_read_domain_t domain;
359         int               title, i, rc, status = EX_SUCCESS;
360
361         for( title = 0; title < TITLE_MAX; title++ )
362                 for( i = 0; i < DOMAIN_MAX; i++ ) {
363                         domain = dvd_read_domains[i];
364                         block = domainblock( &titles[title], domain );
365                         rc = removeblock( blocks, *block );
366                         if( rc == 0 ) {
367                                 printe( 1, "Title %02d %s: block mismatch\n",
368                                   title, domainname( domain ) );
369                                 status |= EX_MISMATCH;
370                         }
371                         if( rc == -1 ) {
372                                 status |= EX_MEM;
373                                 break;
374                         }
375                 }
376
377         return status;
378 }
379
380 /* Remove a block from a block list (if it is contained in a block of the list) */
381 /* Inexistent/invalid blocks are ignored. */
382 /* If the list is increasing the result is also increasing. */
383 /* blocks must contain freeable memory (except for the first one). */
384 static int removeblock( blockl_t blocks, const block_t block )
385 {
386         blockl_t cur, new;
387
388         if( block.size < 0)
389                 return 1;
390
391         for( cur = blocks; cur != NULL; cur = cur->tail )
392                 if
393                         ( cur->block.start <= block.start
394                           && cur->block.start+cur->block.size >= block.start+block.size )
395                 {
396                         /* Allocate a new node */
397                         new = malloc( sizeof( struct blockl ) );
398                         if( ! new ) {
399                                 printe( 1, "memory allocation failed\n" );
400                                 return -1;
401                         }
402                         /* Make a hole */
403                         new->block.start = block.start + block.size;
404                         new->block.size = cur->block.start + cur->block.size - new->block.start;
405                         cur->block.size = block.start - cur->block.start;
406                         /* Insert the new block */
407                         new->tail = cur->tail;
408                         cur->tail = new;
409                         /* Remove empty blocks */
410                         if( new->block.size == 0 ) {
411                                 cur->tail = new->tail;
412                                 free( new );
413                         }
414                         if( cur->block.size == 0 ) {
415                                 new = cur->tail;
416                                 *cur = *cur->tail;
417                                 free( new );
418                         }
419                         return 1;
420                 }
421
422         return 0;
423 }
424
425 /* Iterate over titles and domains and check consistency and copy blocks
426  * corresponding to a VOB domain at the right position */
427 static int decrypttitles( dvd_reader_t *dvd, dvdcss_t dvdcss, int img, titleblocks_t titles[] )
428 {
429         dvd_file_t        *file;
430         block_t           *block;
431         char              blockname[24];
432         dvd_read_domain_t domain;
433         int               title, i, rc, status = EX_NOP;
434
435         for( title = 0; title < TITLE_MAX; title++ )
436                 for( i = 0; i < DOMAIN_MAX; i++ ) {
437                         domain = dvd_read_domains[i];
438                         block = domainblock( &titles[title], domain );
439                         if( dvdread_check )
440                                 file = openfile( dvd, title, domain );
441                         snprintf( blockname, 24, "Title %02d %s", title, domainname( domain ) );
442
443                         /* Checks */
444                         if( dvdread_check && (!!file != !!(block->size >= 0)) ) {
445                                 printe( 1, "ERROR %s: domain mismatch\n", blockname );
446                                 status |= EX_MISMATCH;
447                         }
448                         if( dvdread_check && ! file ) continue;
449                         if( domain == DVD_READ_INFO_FILE )
450                                 printe( 2, "TITLE %02d\n", title );
451                         if( dvdread_check && (DVDFileSize( file ) != (ssize_t)(block->size)) ) {
452                                 printe( 1, "ERROR %s: size mismatch %zd != %d\n",
453                                   blockname, DVDFileSize( file ), block->size );
454                                 status |= EX_MISMATCH;
455                         }
456
457                         /* Decrypt VOBs only */
458                         if( domain != DVD_READ_MENU_VOBS && domain != DVD_READ_TITLE_VOBS )
459                                 rc = copyblock( NULL, dvdcss, img, *block, blockname );
460                         else
461                                 rc = copyblock( dvdread_check ? file : (void *)1,
462                                                 dvdread_decrypt ? NULL : dvdcss, img, *block, blockname );
463                         status |= rc;
464                         status &= ~EX_NOP;
465                         if( rc != EX_SUCCESS )
466                                 printe( 1, "%s: partial decryption\n", blockname );
467
468                         DVDCloseFile( file );
469                 }
470
471         return status;
472 }
473
474 static int copyblocks( dvdcss_t dvdcss, int img, blockl_t blocks )
475 {
476         char blockname[24];
477         int  status = EX_SUCCESS;
478
479         printe( 2, "BLOCKS\n" );
480         for( ; blocks != NULL; blocks = blocks->tail ) {
481                 snprintf( blockname, 24, "Block %08x-%08x",
482                   blocks->block.start, blocks->block.start+blocks->block.size );
483                 status |= copyblock( NULL, dvdcss, img, blocks->block, blockname );
484         }
485
486         if( status )
487                 printe( 1, "error while copying ordinary blocks\n" );
488         return status;
489 }
490
491 /* If file is not NULL, copy/decrypt a title/domain, using libdvdcss for
492  * reading if dvdcss is not NULL, using libdvdread otherwise. */
493 /* If file is NULL, copy an ordinary block (ignoring title and domain). */
494 static int copyblock( dvd_file_t *file, dvdcss_t dvdcss, int img,
495                       block_t block, const char *blockname )
496 {
497         int           lb, rc, status = EX_SUCCESS;
498         int           seek_flags = file ? DVDCSS_SEEK_KEY : DVDCSS_NOFLAGS;
499         int           read_flags = file ? DVDCSS_READ_DECRYPT : DVDCSS_NOFLAGS;
500         /* Aligned read buffer */
501         static unsigned char data[DVD_VIDEO_LB_LEN * 2];
502         unsigned char *buffer =
503                 data + DVD_VIDEO_LB_LEN
504                      - ((long int)data & (DVD_VIDEO_LB_LEN-1));
505
506         if( block.size < 0 ) {
507                 printe( 2, "%s: inva\n", blockname );
508                 return status;
509         }
510         if( file == NULL && dvdcss == NULL ) {
511                 printe( 2, "%s: skip\n", blockname );
512                 return status;
513         }
514         if( block.size == 0 ) {
515                 printe( 2, "%s: null\n", blockname );
516                 return status;
517         }
518
519         /* Seek in the input */
520         if( dvdcss ) {
521                 rc = dvdcss_seek( dvdcss, block.start, seek_flags );
522                 if( rc < 0 ) {
523                         printe( 1, "%s: seeking in the input (dvdcss%s) failed (%s)\n",
524                           blockname, seek_flags & DVDCSS_SEEK_KEY ? " key" : "",
525                           dvdcss_error( dvdcss ) );
526                         status |= EX_IO;
527                 }
528         }
529
530         printe( 2, "%s: ", blockname );
531         progress( -1 );
532
533         /* Seek to to the right position in the output image */
534         rc = ( lseek( img, (off_t)(block.start) * DVD_VIDEO_LB_LEN, SEEK_SET )
535                == (off_t)(-1) );
536         if( rc ) {
537                 progress( 101 );
538                 printe( 1, "%s: seeking in the image failed (%s)\n",
539                   blockname, strerror( errno ) );
540                 status |= EX_IO;
541                 return status;
542         }
543
544         for( lb = 0, progress( 0 ); lb < block.size; lb++ ) {
545                 /* Read one sector (possibly decrypted) */
546                 if( dvdcss )
547                         rc = ( dvdcss_read( dvdcss, buffer, 1, read_flags ) != 1 );
548                 else
549                         rc = ( DVDReadBlocks( file, lb, 1, buffer ) == (ssize_t)(-1) );
550                 if( rc ) {
551                         progress( 101 );
552                         if( file )
553                                 printe( 1, "%s: reading sector %d failed\n", blockname, lb );
554                         status |= EX_IO;
555                         return status;
556                 }
557
558                 /* Write the data */
559                 rc = ( write( img, (void *)buffer, DVD_VIDEO_LB_LEN ) == (ssize_t)(-1) );
560                 if( rc ) {
561                         progress( 101 );
562                         printe( 1, "%s: writing sector %d failed (%s)\n",
563                           blockname, lb, strerror( errno ) );
564                         status |= EX_IO;
565                         return status;
566                 }
567                 progress( (int)(lb*100/block.size) );
568         }
569
570         progress( 100 );
571         return status;
572 }
573
574 /* Test for file existence before open (to silence libdvdnav) */
575 dvd_file_t *openfile( dvd_reader_t *dvd, int title, dvd_read_domain_t domain )
576 {
577         static dvd_stat_t stat;
578         if( ! DVDFileStat( dvd, title, domain, &stat ) )
579                 return DVDOpenFile( dvd, title, domain );
580         return NULL;
581 }
582
583 /* Keep a percentage indicator at the end of the line */
584 static int progress( const int perc )
585 {
586         static int last = 0;
587         if( perc >= 101 ) { /* abort */
588                 last = 0;
589                 printe( 2, "\n" );
590                 fflush( stdout );
591         }
592         else if( perc < 0 ) { /* init */
593                 last = 0;
594                 if( verbosity > 2 )
595                         printe( 3, "   %%" );
596                 fflush( stdout );
597         }
598         else if( perc == 100 ) { /* finish */
599                 last = 0;
600                 if( verbosity > 2 )
601                         printe( 3, "\b\b\b\b100%%\n" );
602                 else
603                         printe( 2, "done\n" );
604                 fflush( stdout );
605         }
606         else if( perc != last ) { /* update */
607                 last = perc;
608                 if( verbosity > 2 )     {
609                         printe( 2, "\b\b\b\b% 3d%%", perc );
610                         fflush( stdout );
611                         return 1;
612                 }
613         }
614         return 0;
615 }
616
617 /* Print on stdout/stderr depending on the verbosity level */
618 int printe( const char level, const char *format, ... )
619 {
620         va_list arg;
621         int     ret;
622         FILE   *stream = stdout;
623         if( level <= 1 )
624                 stream = stderr;
625         if( level > verbosity )
626                 return 1;
627
628         va_start( arg, format );
629         if( level <= 1 )
630                 fprintf( stream, "%s: ", progname );
631         ret = vfprintf( stream, format, arg );
632         va_end( arg );
633         return ret;
634 }