dvdimgdecss.c: main(): also copy the non-decrypted parts
[cdimgtools.git] / dvdimgdecss.c
blob06b84af1cb780d4d5f28335de7df7126151063fb
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 */
8 #include <stdlib.h>
9 #include <stdio.h>
10 #include <getopt.h>
11 #include <errno.h>
12 #include <string.h>
13 #include <sys/types.h>
14 #include <unistd.h>
15 #include <sys/stat.h>
16 #include <fcntl.h>
17 #include <dvdread/dvd_reader.h>
18 #include <dvdread/dvd_udf.h>
19 #include <dvdcss/dvdcss.h>
21 #define EX_SUCCESS 0
22 #define EX_USAGE (~((~0)<<8))
23 #define EX_OPEN (~((~0)<<7))
24 #define EX_IO (1<<6)
25 #define EX_MISMATCH (1<<5)
26 #define EX_MEM (1<<4)
27 #define EX_NOP (1<<3)
28 const char *progname = "dvdimgdecss";
29 char verbosity = 1;
30 #define TITLE_MAX 100
32 /* Make an array of an enum so as to iterate */
33 #define DOMAIN_MAX 4
34 const dvd_read_domain_t dvd_read_domains[DOMAIN_MAX] = {
35 DVD_READ_INFO_FILE,
36 DVD_READ_MENU_VOBS,
37 DVD_READ_TITLE_VOBS,
38 DVD_READ_INFO_BACKUP_FILE,
41 static const char *domainname( dvd_read_domain_t domain )
43 switch( domain ) {
44 case DVD_READ_INFO_FILE:
45 return "INFO";
46 case DVD_READ_MENU_VOBS:
47 return "MENU";
48 case DVD_READ_TITLE_VOBS:
49 return "VOBS";
50 case DVD_READ_INFO_BACKUP_FILE:
51 return "IBUP";
52 default:
53 return "Unknown";
57 /* A negative size means inexistent; a zero size means empty */
58 typedef struct {
59 int start;
60 int size;
61 } block_t;
63 typedef struct {
64 block_t ifo, menu, vob, bup;
65 } titleblocks_t;
67 block_t *domainblock( titleblocks_t *tblocks, dvd_read_domain_t domain )
69 if( ! tblocks ) return NULL;
70 switch( domain ) {
71 case DVD_READ_INFO_FILE:
72 return &tblocks->ifo;
73 case DVD_READ_MENU_VOBS:
74 return &tblocks->menu;
75 case DVD_READ_TITLE_VOBS:
76 return &tblocks->vob;
77 case DVD_READ_INFO_BACKUP_FILE:
78 return &tblocks->bup;
79 default:
80 return NULL;
84 typedef struct blockl {
85 block_t block;
86 struct blockl *tail;
87 } *blockl_t;
89 static void usage( )
91 printf( "Usage:\n" );
92 printf( "\t%s <dvd>\n", progname );
93 printf( "\t%s [-f] [-c] <dvd> <out_file>\n", progname );
96 static int dvdsize ( const char * );
97 static int savetitleblocks( dvd_reader_t *, titleblocks_t (*)[TITLE_MAX] );
98 static int fileblock ( dvd_reader_t *, char *, block_t * );
99 static int removetitles ( blockl_t, titleblocks_t [] );
100 static int removeblock ( blockl_t, const block_t );
101 static int decrypttitles ( dvd_reader_t *, dvdcss_t, int, titleblocks_t [] );
102 static int copyblocks ( dvdcss_t, int, blockl_t );
103 static int copyblock ( dvd_file_t *, dvdcss_t, int, block_t, const char * );
104 dvd_file_t *openfile ( dvd_reader_t *, int, dvd_read_domain_t );
105 static int progress ( const int );
107 /* Main for a command line tool */
108 int main( int argc, char *argv[] )
110 char *dvdfile, *imgfile = NULL;
111 dvd_reader_t *dvd;
112 dvdcss_t dvdcss = NULL;
113 int img;
114 titleblocks_t titles[TITLE_MAX];
115 struct blockl blocks;
116 int rc, status = EX_SUCCESS;
118 setvbuf( stdout, NULL, _IOLBF, BUFSIZ );
120 /* Options */
121 char fcreate = 0, dvdcssread = 0;
122 extern int optind;
123 while( (rc = getopt( argc, argv, "fc" )) != -1 )
124 switch( (char)rc ) {
125 case 'f':
126 fcreate = 1;
127 break;
128 case 'c':
129 dvdcssread = 1;
130 break;
131 case '?':
132 default:
133 usage( );
134 exit( EX_USAGE );
136 argc -= optind;
137 argv += optind;
139 /* Command line args */
140 if( argc < 1 || argc > 2 ) {
141 fprintf( stderr, "%s: syntax error\n", progname );
142 usage( );
143 exit( EX_USAGE );
145 dvdfile = argv[0];
146 if( argc == 2 ) imgfile = argv[1];
147 if( ! imgfile ) dvdcssread = 0; /* libdvdcss not used if no image written */
149 /* Open the DVD */
150 dvd = DVDOpen( dvdfile );
151 if( dvd == NULL ) {
152 fprintf( stderr, "%s: opening of the DVD (%s) failed\n",
153 progname, dvdfile );
154 exit( status | EX_OPEN );
156 if( dvdcssread ) {
157 dvdcss = dvdcss_open( dvdfile );
158 if( dvdcss == NULL ) {
159 fprintf( stderr, "%s: opening of the DVD (%s) with libdvdcss failed\n",
160 progname, dvdfile );
161 exit( status | EX_OPEN );
165 /* Search the DVD for the positions of the title files */
166 blocks.tail = NULL;
167 blocks.block.start = 0;
168 blocks.block.size = dvdsize( dvdfile );
169 printf( "%s: DVD end at 0x%08x\n", progname, blocks.block.size );
170 status |= savetitleblocks( dvd, &titles );
171 if( dvdcssread ) {
172 if( blocks.block.size < 0 ) {
173 fprintf( stderr, "%s: cannot determine the size of the DVD\n", progname );
174 blocks.block.size = 0;
176 else
177 status |= removetitles( &blocks, titles );
180 /* Check & Decrypt & Write */
181 if( imgfile ) {
182 img = open( imgfile, fcreate ? O_RDWR | O_CREAT : O_RDWR,
183 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH );
184 if( img < 0 ) {
185 fprintf( stderr, "%s: opening of the image file (%s) failed (%s)\n",
186 progname, imgfile, strerror( errno ) );
187 status |= EX_OPEN;
189 else {
190 printf( "\n" );
191 status |= decrypttitles( dvd, dvdcss, img, titles );
192 status |= copyblocks( dvdcss, img, &blocks );
193 if( close( img ) < 0 ) {
194 fprintf( stderr, "%s: closing of the image file failed (%s)\n",
195 progname, strerror( errno ) );
196 status |= EX_IO;
201 /* Close DVD */
202 DVDClose( dvd );
203 if( dvdcss && (dvdcss_close( dvdcss ) < 0) ) {
204 fprintf( stderr, "closing of the DVD with libdvdcss failed\n" );
205 status |= EX_IO;
207 exit( status );
210 /* Return the size in sectors (whether it is a file or a special device) */
211 static int dvdsize( const char *dvdfile )
213 off_t size;
214 struct stat buf;
215 int dvd, rc;
217 rc = stat( dvdfile, &buf );
218 if( rc < 0 ) {
219 fprintf( stderr, "%s: stat DVD (%s) failed (%s)\n",
220 progname, dvdfile, strerror( errno ) );
221 return -1;
224 if( !buf.st_rdev )
225 size = buf.st_size;
226 else {
227 dvd = open( dvdfile, O_RDONLY );
228 if( dvd < 0 ) {
229 fprintf( stderr, "%s: opening the DVD (%s) failed (%s)\n",
230 progname, dvdfile, strerror( errno ) );
231 return -1;
233 size = lseek( dvd, 0, SEEK_END );
234 if( size < 0 ) {
235 fprintf( stderr, "%s: seeking at the end of the DVD failed (%s)\n",
236 progname, strerror( errno ) );
237 return -1;
239 if( close( dvd ) < 0 )
240 fprintf( stderr, "%s: closing of the DVD failed (%s)\n",
241 progname, strerror( errno ) );
244 if( size % DVD_VIDEO_LB_LEN )
245 fprintf( stderr, "%s: DVD size is not a block multiple\n", progname );
246 return size / DVD_VIDEO_LB_LEN;
249 /* Save the sector positions of the title/domain files */
250 static int savetitleblocks( dvd_reader_t *dvd, titleblocks_t (*titles)[TITLE_MAX] )
252 int status = EX_SUCCESS;
253 char filename[32]; /* MAX_UDF_FILE_NAME_LEN too much */
254 titleblocks_t *tblocks;
255 block_t block;
256 int count = 0, title, i, start;
258 /* Video Manager */
259 title = 0;
260 tblocks = &(*titles)[title];
261 sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "IFO" );
262 fileblock( dvd, filename, &tblocks->ifo )
263 || printf( "%s: WARNING %s not found\n", progname, filename );
264 sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "VOB" );
265 fileblock( dvd, filename, &tblocks->menu );
266 fileblock( dvd, "/VIDEO_TS/invalid_name/", &tblocks->vob );
267 sprintf( filename, "/VIDEO_TS/VIDEO_TS.%s", "BUP" );
268 fileblock( dvd, filename, &tblocks->bup );
270 /* Titles */
271 for( title = 1; title < TITLE_MAX; title++ ) {
272 tblocks = &(*titles)[title];
273 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "IFO" );
274 fileblock( dvd, filename, &tblocks->ifo )
275 && count++;
276 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "VOB" );
277 fileblock( dvd, filename, &tblocks->menu );
278 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 1, "VOB" );
279 fileblock( dvd, filename, &tblocks->vob );
280 for( i = 2, start = tblocks->vob.start+tblocks->vob.size; i < 10; i++ ) {
281 /* Title VOBs may be split into several files */
282 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, i, "VOB" );
283 if( fileblock( dvd, filename, &block ) ) {
284 tblocks->vob.size += block.size;
285 if( block.start != start ) {
286 fprintf( stderr, "%s: WARNING whole in title %d before part %d\n",
287 progname, title, i );
288 status |= EX_MISMATCH;
290 start = block.start+block.size;
293 sprintf( filename, "/VIDEO_TS/VTS_%02d_%d.%s", title, 0, "BUP" );
294 fileblock( dvd, filename, &tblocks->bup );
297 printf( "%s: %d titles found\n", progname, count );
298 return status;
301 /* Record the sector range over which a file spans */
302 static int fileblock( dvd_reader_t *dvd, char *filename, block_t *block )
304 uint32_t sector, size;
305 (*block).start = 0;
306 (*block).size = -1;
308 sector = UDFFindFile( dvd, filename, &size );
309 if( sector ) {
310 if( size % DVD_VIDEO_LB_LEN )
311 fprintf( stderr, "%s: WARNING size of %s is not a block multiple\n",
312 progname, filename );
313 size /= DVD_VIDEO_LB_LEN;
314 (*block).start = sector;
315 (*block).size = size;
316 printf( "%s: %s at 0x%08x-0x%08x\n",
317 progname, filename, sector, sector+size );
318 return 1;
321 return 0;
324 /* Remove from blocks the block sectors of all title/domains */
325 static int removetitles( blockl_t blocks, titleblocks_t titles[] )
327 block_t *block;
328 dvd_read_domain_t domain;
329 int title, i, rc, status = EX_SUCCESS;
331 for( title = 0; title < TITLE_MAX; title++ )
332 for( i = 0; i < DOMAIN_MAX; i++ ) {
333 domain = dvd_read_domains[i];
334 block = domainblock( &titles[title], domain );
335 rc = removeblock( blocks, *block );
336 if( rc == 0 ) {
337 fprintf( stderr, "%s: Title %02d %s: block mismatch\n",
338 progname, title, domainname( domain ) );
339 status |= EX_MISMATCH;
341 if( rc == -1 ) {
342 status |= EX_MEM;
343 break;
347 return status;
350 /* Remove a block from a block list (if it is contained in a block of the list) */
351 /* Inexistent/invalid blocks are ignored. */
352 /* If the list is increasing the result is also increasing. */
353 /* blocks must contain freeable memory (except for the first one). */
354 static int removeblock( blockl_t blocks, const block_t block )
356 blockl_t cur, new;
358 if( block.size < 0)
359 return 1;
361 for( cur = blocks; cur != NULL; cur = cur->tail )
363 ( cur->block.start <= block.start
364 && cur->block.start+cur->block.size >= block.start+block.size )
366 /* Allocate a new node */
367 new = malloc( sizeof( struct blockl ) );
368 if( ! new ) {
369 fprintf( stderr, "%s: memory allocation failed\n", progname );
370 return -1;
372 /* Make a hole */
373 new->block.start = block.start + block.size;
374 new->block.size = cur->block.start + cur->block.size - new->block.start;
375 cur->block.size = block.start - cur->block.start;
376 /* Insert the new block */
377 new->tail = cur->tail;
378 cur->tail = new;
379 /* Remove empty blocks */
380 if( new->block.size == 0 ) {
381 cur->tail = new->tail;
382 free( new );
384 if( cur->block.size == 0 ) {
385 new = cur->tail;
386 *cur = *cur->tail;
387 free( new );
389 return 1;
392 return 0;
395 /* Iterate over titles and domains and check consistency and copy blocks
396 * corresponding to a VOB domain at the right position */
397 static int decrypttitles( dvd_reader_t *dvd, dvdcss_t dvdcss, int img, titleblocks_t titles[] )
399 dvd_file_t *file;
400 block_t *block;
401 char blockname[24];
402 dvd_read_domain_t domain;
403 int title, i, rc, status = EX_NOP;
405 for( title = 0; title < TITLE_MAX; title++ )
406 for( i = 0; i < DOMAIN_MAX; i++ ) {
407 domain = dvd_read_domains[i];
408 block = domainblock( &titles[title], domain );
409 file = openfile( dvd, title, domain );
410 snprintf( blockname, 24, "Title %02d %s", title, domainname( domain ) );
412 /* Checks */
413 if( !!file != !!(block->size >= 0) ) {
414 fprintf( stderr, "%s: ERROR %s: domain mismatch\n",
415 progname, blockname );
416 status |= EX_MISMATCH;
418 if( ! file ) continue;
419 if( domain == DVD_READ_INFO_FILE )
420 printf( "TITLE %02d\n", title );
421 if( DVDFileSize( file ) != (ssize_t)(block->size) ) {
422 fprintf( stderr, "%s: ERROR %s: size mismatch %zd != %d\n",
423 progname, blockname, DVDFileSize( file ), block->size );
424 status |= EX_MISMATCH;
427 /* Decrypt VOBs only */
428 if( domain != DVD_READ_MENU_VOBS && domain != DVD_READ_TITLE_VOBS )
429 rc = copyblock( NULL, dvdcss, img, *block, blockname );
430 else
431 rc = copyblock( file, dvdcss, img, *block, blockname );
432 status |= rc;
433 status &= ~EX_NOP;
434 if( rc != EX_SUCCESS )
435 fprintf( stderr, "%s: %s: partial decryption\n",
436 progname, blockname );
438 DVDCloseFile( file );
441 return status;
444 static int copyblocks( dvdcss_t dvdcss, int img, blockl_t blocks )
446 char blockname[24];
447 int status = EX_SUCCESS;
449 if( dvdcss == NULL ) {
450 fprintf( stderr, "%s: cannot copy ordinary blocks without libdvdcss\n",
451 progname );
452 status |= EX_IO;
455 printf( "BLOCKS\n" );
456 for( ; blocks != NULL; blocks = blocks->tail ) {
457 snprintf( blockname, 24, "Block %08x-%08x",
458 blocks->block.start, blocks->block.start+blocks->block.size );
459 status |= copyblock( NULL, dvdcss, img, blocks->block, blockname );
462 if( status )
463 fprintf( stderr, "%s: error while copying ordinary blocks\n", progname );
464 return status;
467 /* If file is not NULL, copy/decrypt a title/domain, using libdvdcss for
468 * reading if dvdcss is not NULL, using libdvdread otherwise. */
469 /* If file is NULL, copy an ordinary block (ignoring title and domain). */
470 static int copyblock( dvd_file_t *file, dvdcss_t dvdcss, int img,
471 block_t block, const char *blockname )
473 int lb, rc, status = EX_SUCCESS;
474 int seek_flags = file ? DVDCSS_SEEK_KEY : DVDCSS_NOFLAGS;
475 int read_flags = file ? DVDCSS_READ_DECRYPT : DVDCSS_NOFLAGS;
476 /* Aligned read buffer */
477 static unsigned char data[DVD_VIDEO_LB_LEN * 2];
478 unsigned char *buffer =
479 data + DVD_VIDEO_LB_LEN
480 - ((long int)data & (DVD_VIDEO_LB_LEN-1));
482 if( block.size < 0 ) {
483 printf( "%s: inva\n", blockname );
484 return status;
486 if( file == NULL && dvdcss == NULL ) {
487 printf( "%s: skip\n", blockname );
488 return status;
490 if( block.size == 0 ) {
491 printf( "%s: null\n", blockname );
492 return status;
495 /* Seek in the input */
496 if( dvdcss ) {
497 rc = dvdcss_seek( dvdcss, block.start, seek_flags );
498 if( rc < 0 ) {
499 fprintf( stderr, "%s: %s: seeking in the input (dvdcss%s) failed (%s)\n",
500 progname, blockname, seek_flags & DVDCSS_SEEK_KEY ? " key" : "",
501 dvdcss_error( dvdcss ) );
502 status |= EX_IO;
506 printf( "%s: ", blockname );
507 progress( -1 );
509 /* Seek to to the right position in the output image */
510 rc = ( lseek( img, (off_t)(block.start) * DVD_VIDEO_LB_LEN, SEEK_SET )
511 == (off_t)(-1) );
512 if( rc ) {
513 progress( 101 );
514 fprintf( stderr, "%s: %s: seeking in the image failed (%s)\n",
515 progname, blockname, strerror( errno ) );
516 status |= EX_IO;
517 return status;
520 for( lb = 0, progress( 0 ); lb < block.size; lb++ ) {
521 /* Read one sector (possibly decrypted) */
522 if( dvdcss )
523 rc = ( dvdcss_read( dvdcss, buffer, 1, read_flags ) != 1 );
524 else
525 rc = ( DVDReadBlocks( file, lb, 1, buffer ) == (ssize_t)(-1) );
526 if( rc ) {
527 progress( 101 );
528 if( file )
529 fprintf( stderr, "%s: %s: reading sector %d failed\n",
530 progname, blockname, lb );
531 status |= EX_IO;
532 return status;
535 /* Write the data */
536 rc = ( write( img, (void *)buffer, DVD_VIDEO_LB_LEN ) == (ssize_t)(-1) );
537 if( rc ) {
538 progress( 101 );
539 fprintf( stderr, "%s: %s: writing sector %d failed (%s)\n",
540 progname, blockname, lb, strerror( errno ) );
541 status |= EX_IO;
542 return status;
544 progress( (int)(lb*100/block.size) );
547 progress( 100 );
548 return status;
551 /* Test for file existence before open (to silence libdvdnav) */
552 dvd_file_t *openfile( dvd_reader_t *dvd, int title, dvd_read_domain_t domain )
554 static dvd_stat_t stat;
555 if( ! DVDFileStat( dvd, title, domain, &stat ) )
556 return DVDOpenFile( dvd, title, domain );
557 return NULL;
560 /* Keep a percentage indicator at the end of the line */
561 static int progress( const int perc )
563 static int last = 0;
564 if( perc >= 101 ) { /* abort */
565 last = 0;
566 printf( "\n" );
567 fflush( stdout );
569 else if( perc < 0 ) { /* init */
570 last = 0;
571 printf( " %%" );
572 fflush( stdout );
574 else if( perc == 100 ) { /* finish */
575 last = 0;
576 printf( "\b\b\b\b100%%\n" );
577 fflush( stdout );
579 else if( perc != last ) { /* update */
580 last = perc;
581 printf( "\b\b\b\b% 3d%%", perc );
582 fflush( stdout );
583 return 1;
585 return 0;