change the package "pretty name" to CDimg|tools
[cdimgtools.git] / dvdimgdecss.c
blob0b7006d9275cbd9ed172c05adec7e72b8e0a29ed
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.
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.
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/>.
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>
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
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,
62 static const char *domainname( dvd_read_domain_t domain )
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";
78 /* A negative size means inexistent; a zero size means empty */
79 typedef struct {
80 int start;
81 int size;
82 } block_t;
84 typedef struct {
85 block_t ifo, menu, vob, bup;
86 } titleblocks_t;
88 block_t *domainblock( titleblocks_t *tblocks, dvd_read_domain_t domain )
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;
105 typedef struct blockl {
106 block_t block;
107 struct blockl *tail;
108 } *blockl_t;
110 static void usage( )
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 );
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 *, ... );
130 /* Main for a command line tool */
131 int main( int argc, char *argv[] )
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;
141 setvbuf( stdout, NULL, _IOLBF, BUFSIZ );
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 );
169 argc -= optind;
170 argv += optind;
172 /* Command line args */
173 if( argc < 1 || argc > 2 ) {
174 printe( 1, "syntax error\n" );
175 usage( );
176 exit( EX_USAGE );
178 dvdfile = argv[0];
179 if( argc == 2 ) imgfile = argv[1];
180 if( !imgfile ) verbosity++;
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 );
190 dvd = DVDOpen( dvdfile );
191 if( dvd == NULL ) {
192 printe( 1, "opening of the DVD (%s) failed\n", dvdfile );
193 exit( status | EX_OPEN );
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;
206 else
207 status |= removetitles( &blocks, titles );
209 /* Make libdvdread try to get all the title keys now */
210 if( dvdread_check ) openfile( dvd, 0, DVD_READ_MENU_VOBS );
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;
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;
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;
239 exit( status );
242 /* Return the size in sectors (whether it is a file or a special device) */
243 static int dvdsize( const char *dvdfile )
245 off_t size;
246 struct stat buf;
247 int dvd, rc;
249 rc = stat( dvdfile, &buf );
250 if( rc < 0 ) {
251 printe( 1, "stat DVD (%s) failed (%s)\n", dvdfile, strerror( errno ) );
252 return -1;
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;
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;
270 if( close( dvd ) < 0 )
271 printe( 1, "closing of the DVD failed (%s)\n", strerror( errno ) );
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;
279 /* Save the sector positions of the title/domain files */
280 static int savetitleblocks( dvd_reader_t *dvd, titleblocks_t (*titles)[TITLE_MAX] )
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;
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 );
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;
320 start = block.start+block.size;
323 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "BUP" );
324 fileblock( dvd, filename, &tblocks->bup );
327 printe( 3, "%s: %d titles found\n", progname, count );
328 return status;
331 /* Record the sector range over which a file spans */
332 static int fileblock( dvd_reader_t *dvd, char *filename, block_t *block )
334 uint32_t sector, size;
335 (*block).start = 0;
336 (*block).size = -1;
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;
351 return 0;
354 /* Remove from blocks the block sectors of all title/domains */
355 static int removetitles( blockl_t blocks, titleblocks_t titles[] )
357 block_t *block;
358 dvd_read_domain_t domain;
359 int title, i, rc, status = EX_SUCCESS;
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;
371 if( rc == -1 ) {
372 status |= EX_MEM;
373 break;
377 return status;
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 )
386 blockl_t cur, new;
388 if( block.size < 0)
389 return 1;
391 for( cur = blocks; cur != NULL; cur = cur->tail )
393 ( cur->block.start <= block.start
394 && cur->block.start+cur->block.size >= block.start+block.size )
396 /* Allocate a new node */
397 new = malloc( sizeof( struct blockl ) );
398 if( ! new ) {
399 printe( 1, "memory allocation failed\n" );
400 return -1;
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 );
414 if( cur->block.size == 0 ) {
415 new = cur->tail;
416 *cur = *cur->tail;
417 free( new );
419 return 1;
422 return 0;
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[] )
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;
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 ) );
443 /* Checks */
444 if( dvdread_check && (!!file != !!(block->size >= 0)) ) {
445 printe( 1, "ERROR %s: domain mismatch\n", blockname );
446 status |= EX_MISMATCH;
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;
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 );
468 DVDCloseFile( file );
471 return status;
474 static int copyblocks( dvdcss_t dvdcss, int img, blockl_t blocks )
476 char blockname[24];
477 int status = EX_SUCCESS;
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 );
486 if( status )
487 printe( 1, "error while copying ordinary blocks\n" );
488 return status;
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 )
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));
506 if( block.size < 0 ) {
507 printe( 2, "%s: inva\n", blockname );
508 return status;
510 if( file == NULL && dvdcss == NULL ) {
511 printe( 2, "%s: skip\n", blockname );
512 return status;
514 if( block.size == 0 ) {
515 printe( 2, "%s: null\n", blockname );
516 return status;
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;
530 printe( 2, "%s: ", blockname );
531 progress( -1 );
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;
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;
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;
567 progress( (int)(lb*100/block.size) );
570 progress( 100 );
571 return status;
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 )
577 static dvd_stat_t stat;
578 if( ! DVDFileStat( dvd, title, domain, &stat ) )
579 return DVDOpenFile( dvd, title, domain );
580 return NULL;
583 /* Keep a percentage indicator at the end of the line */
584 static int progress( const int perc )
586 static int last = 0;
587 if( perc >= 101 ) { /* abort */
588 last = 0;
589 printe( 2, "\n" );
590 fflush( stdout );
592 else if( perc < 0 ) { /* init */
593 last = 0;
594 if( verbosity > 2 )
595 printe( 3, " %%" );
596 fflush( stdout );
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 );
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;
614 return 0;
617 /* Print on stdout/stderr depending on the verbosity level */
618 int printe( const char level, const char *format, ... )
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;
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;