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.
13 #include <sys/types.h>
17 #include <dvdread/dvd_reader.h>
18 #include <dvdread/dvd_udf.h>
19 #include <dvdcss/dvdcss.h>
22 #define EX_USAGE (~((~0)<<8))
23 #define EX_OPEN (~((~0)<<7))
25 #define EX_MISMATCH (1<<5)
28 const char *progname
= "dvdimgdecss";
32 /* Make an array of an enum so as to iterate */
34 const dvd_read_domain_t dvd_read_domains
[DOMAIN_MAX
] = {
38 DVD_READ_INFO_BACKUP_FILE
,
41 static const char *domainname( dvd_read_domain_t domain
)
44 case DVD_READ_INFO_FILE
:
46 case DVD_READ_MENU_VOBS
:
48 case DVD_READ_TITLE_VOBS
:
50 case DVD_READ_INFO_BACKUP_FILE
:
57 /* A negative size means inexistent; a zero size means empty */
64 block_t ifo
, menu
, vob
, bup
;
67 block_t
*domainblock( titleblocks_t
*tblocks
, dvd_read_domain_t domain
)
69 if( ! tblocks
) return NULL
;
71 case DVD_READ_INFO_FILE
:
73 case DVD_READ_MENU_VOBS
:
74 return &tblocks
->menu
;
75 case DVD_READ_TITLE_VOBS
:
77 case DVD_READ_INFO_BACKUP_FILE
:
84 typedef struct blockl
{
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
;
112 dvdcss_t dvdcss
= NULL
;
114 titleblocks_t titles
[TITLE_MAX
];
115 struct blockl blocks
;
116 int rc
, status
= EX_SUCCESS
;
118 setvbuf( stdout
, NULL
, _IOLBF
, BUFSIZ
);
121 char fcreate
= 0, dvdcssread
= 0;
123 while( (rc
= getopt( argc
, argv
, "fc" )) != -1 )
139 /* Command line args */
140 if( argc
< 1 || argc
> 2 ) {
141 fprintf( stderr
, "%s: syntax error\n", progname
);
146 if( argc
== 2 ) imgfile
= argv
[1];
147 if( ! imgfile
) dvdcssread
= 0; /* libdvdcss not used if no image written */
150 dvd
= DVDOpen( dvdfile
);
152 fprintf( stderr
, "%s: opening of the DVD (%s) failed\n",
154 exit( status
| EX_OPEN
);
157 dvdcss
= dvdcss_open( dvdfile
);
158 if( dvdcss
== NULL
) {
159 fprintf( stderr
, "%s: opening of the DVD (%s) with libdvdcss failed\n",
161 exit( status
| EX_OPEN
);
165 /* Search the DVD for the positions of the title files */
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
);
172 if( blocks
.block
.size
< 0 ) {
173 fprintf( stderr
, "%s: cannot determine the size of the DVD\n", progname
);
174 blocks
.block
.size
= 0;
177 status
|= removetitles( &blocks
, titles
);
180 /* Check & Decrypt & Write */
182 img
= open( imgfile
, fcreate
? O_RDWR
| O_CREAT
: O_RDWR
,
183 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
185 fprintf( stderr
, "%s: opening of the image file (%s) failed (%s)\n",
186 progname
, imgfile
, strerror( errno
) );
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
) );
203 if( dvdcss
&& (dvdcss_close( dvdcss
) < 0) ) {
204 fprintf( stderr
, "closing of the DVD with libdvdcss failed\n" );
210 /* Return the size in sectors (whether it is a file or a special device) */
211 static int dvdsize( const char *dvdfile
)
217 rc
= stat( dvdfile
, &buf
);
219 fprintf( stderr
, "%s: stat DVD (%s) failed (%s)\n",
220 progname
, dvdfile
, strerror( errno
) );
227 dvd
= open( dvdfile
, O_RDONLY
);
229 fprintf( stderr
, "%s: opening the DVD (%s) failed (%s)\n",
230 progname
, dvdfile
, strerror( errno
) );
233 size
= lseek( dvd
, 0, SEEK_END
);
235 fprintf( stderr
, "%s: seeking at the end of the DVD failed (%s)\n",
236 progname
, strerror( errno
) );
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
;
256 int count
= 0, title
, i
, start
;
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
);
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
)
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
);
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
;
308 sector
= UDFFindFile( dvd
, filename
, &size
);
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
);
324 /* Remove from blocks the block sectors of all title/domains */
325 static int removetitles( blockl_t blocks
, titleblocks_t titles
[] )
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
);
337 fprintf( stderr
, "%s: Title %02d %s: block mismatch\n",
338 progname
, title
, domainname( domain
) );
339 status
|= EX_MISMATCH
;
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
)
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
) );
369 fprintf( stderr
, "%s: memory allocation failed\n", progname
);
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
;
379 /* Remove empty blocks */
380 if( new->block
.size
== 0 ) {
381 cur
->tail
= new->tail
;
384 if( cur
->block
.size
== 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
[] )
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
) );
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
);
431 rc
= copyblock( file
, dvdcss
, img
, *block
, blockname
);
434 if( rc
!= EX_SUCCESS
)
435 fprintf( stderr
, "%s: %s: partial decryption\n",
436 progname
, blockname
);
438 DVDCloseFile( file
);
444 static int copyblocks( dvdcss_t dvdcss
, int img
, blockl_t blocks
)
447 int status
= EX_SUCCESS
;
449 if( dvdcss
== NULL
) {
450 fprintf( stderr
, "%s: cannot copy ordinary blocks without libdvdcss\n",
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
);
463 fprintf( stderr
, "%s: error while copying ordinary blocks\n", progname
);
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
);
486 if( file
== NULL
&& dvdcss
== NULL
) {
487 printf( "%s: skip\n", blockname
);
490 if( block
.size
== 0 ) {
491 printf( "%s: null\n", blockname
);
495 /* Seek in the input */
497 rc
= dvdcss_seek( dvdcss
, block
.start
, seek_flags
);
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
) );
506 printf( "%s: ", blockname
);
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
)
514 fprintf( stderr
, "%s: %s: seeking in the image failed (%s)\n",
515 progname
, blockname
, strerror( errno
) );
520 for( lb
= 0, progress( 0 ); lb
< block
.size
; lb
++ ) {
521 /* Read one sector (possibly decrypted) */
523 rc
= ( dvdcss_read( dvdcss
, buffer
, 1, read_flags
) != 1 );
525 rc
= ( DVDReadBlocks( file
, lb
, 1, buffer
) == (ssize_t
)(-1) );
529 fprintf( stderr
, "%s: %s: reading sector %d failed\n",
530 progname
, blockname
, lb
);
536 rc
= ( write( img
, (void *)buffer
, DVD_VIDEO_LB_LEN
) == (ssize_t
)(-1) );
539 fprintf( stderr
, "%s: %s: writing sector %d failed (%s)\n",
540 progname
, blockname
, lb
, strerror( errno
) );
544 progress( (int)(lb
*100/block
.size
) );
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
);
560 /* Keep a percentage indicator at the end of the line */
561 static int progress( const int perc
)
564 if( perc
>= 101 ) { /* abort */
569 else if( perc
< 0 ) { /* init */
574 else if( perc
== 100 ) { /* finish */
576 printf( "\b\b\b\b100%%\n" );
579 else if( perc
!= last
) { /* update */
581 printf( "\b\b\b\b% 3d%%", perc
);