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
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/>.
25 #include <sys/types.h>
29 #include <dvdread/dvd_reader.h>
30 #include <dvdread/dvd_udf.h>
31 #include <dvdcss/dvdcss.h>
34 #define EX_USAGE (~((~0)<<8))
35 #define EX_OPEN (~((~0)<<7))
37 #define EX_MISMATCH (1<<5)
41 # define PROGRAM_NAME "dvdimgdecss"
43 #ifndef PROGRAM_VERSION
44 # define PROGRAM_VERSION "0.1"
46 const char *progname
= PROGRAM_NAME
;
47 const char *progversion
= PROGRAM_VERSION
;
49 char dvdread_check
= 0;
50 char dvdread_decrypt
= 0;
53 /* Make an array of an enum so as to iterate */
55 const dvd_read_domain_t dvd_read_domains
[DOMAIN_MAX
] = {
59 DVD_READ_INFO_BACKUP_FILE
,
62 static const char *domainname( dvd_read_domain_t domain
)
65 case DVD_READ_INFO_FILE
:
67 case DVD_READ_MENU_VOBS
:
69 case DVD_READ_TITLE_VOBS
:
71 case DVD_READ_INFO_BACKUP_FILE
:
78 /* A negative size means inexistent; a zero size means empty */
85 block_t ifo
, menu
, vob
, bup
;
88 block_t
*domainblock( titleblocks_t
*tblocks
, dvd_read_domain_t domain
)
90 if( ! tblocks
) return NULL
;
92 case DVD_READ_INFO_FILE
:
94 case DVD_READ_MENU_VOBS
:
95 return &tblocks
->menu
;
96 case DVD_READ_TITLE_VOBS
:
98 case DVD_READ_INFO_BACKUP_FILE
:
105 typedef struct blockl
{
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
;
135 dvdcss_t dvdcss
= NULL
;
137 titleblocks_t titles
[TITLE_MAX
];
138 struct blockl blocks
;
139 int rc
, status
= EX_SUCCESS
;
141 setvbuf( stdout
, NULL
, _IOLBF
, BUFSIZ
);
145 while( (rc
= getopt( argc
, argv
, "qvcCV" )) != -1 )
160 printf( "%s version %s (libdvdcss version %s)\n",
161 progname
, progversion
, dvdcss_interface_2
);
172 /* Command line args */
173 if( argc
< 1 || argc
> 2 ) {
174 printe( 1, "syntax error\n" );
179 if( argc
== 2 ) imgfile
= argv
[1];
180 if( !imgfile
) verbosity
++;
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
);
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 */
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;
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 */
214 img
= open( imgfile
, O_RDWR
| O_CREAT
,
215 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
217 printe( 1, "opening of the image file (%s) failed (%s)\n",
218 imgfile
, strerror( errno
) );
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",
235 if( dvdcss_close( dvdcss
) < 0 ) {
236 printe( 1, "closing of the DVD with libdvdcss failed\n" );
242 /* Return the size in sectors (whether it is a file or a special device) */
243 static int dvdsize( const char *dvdfile
)
249 rc
= stat( dvdfile
, &buf
);
251 printe( 1, "stat DVD (%s) failed (%s)\n", dvdfile
, strerror( errno
) );
258 dvd
= open( dvdfile
, O_RDONLY
);
260 printe( 1, "opening the DVD (%s) failed (%s)\n",
261 dvdfile
, strerror( errno
) );
264 size
= lseek( dvd
, 0, SEEK_END
);
266 printe( 1, "seeking at the end of the DVD failed (%s)\n",
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
;
286 int count
= 0, title
, i
, start
;
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
);
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
)
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",
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
);
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
;
338 sector
= UDFFindFile( dvd
, filename
, &size
);
340 if( size
% DVD_VIDEO_LB_LEN
)
341 printe( 1, "WARNING size of %s is not a block multiple\n",
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
);
354 /* Remove from blocks the block sectors of all title/domains */
355 static int removetitles( blockl_t blocks
, titleblocks_t titles
[] )
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
);
367 printe( 1, "Title %02d %s: block mismatch\n",
368 title
, domainname( domain
) );
369 status
|= EX_MISMATCH
;
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
)
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
) );
399 printe( 1, "memory allocation failed\n" );
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
;
409 /* Remove empty blocks */
410 if( new->block
.size
== 0 ) {
411 cur
->tail
= new->tail
;
414 if( cur
->block
.size
== 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
[] )
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
);
440 file
= openfile( dvd
, title
, domain
);
441 snprintf( blockname
, 24, "Title %02d %s", title
, domainname( domain
) );
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
);
461 rc
= copyblock( dvdread_check
? file
: (void *)1,
462 dvdread_decrypt
? NULL
: dvdcss
, img
, *block
, blockname
);
465 if( rc
!= EX_SUCCESS
)
466 printe( 1, "%s: partial decryption\n", blockname
);
468 DVDCloseFile( file
);
474 static int copyblocks( dvdcss_t dvdcss
, int img
, blockl_t blocks
)
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
);
487 printe( 1, "error while copying ordinary blocks\n" );
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
);
510 if( file
== NULL
&& dvdcss
== NULL
) {
511 printe( 2, "%s: skip\n", blockname
);
514 if( block
.size
== 0 ) {
515 printe( 2, "%s: null\n", blockname
);
519 /* Seek in the input */
521 rc
= dvdcss_seek( dvdcss
, block
.start
, seek_flags
);
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
) );
530 printe( 2, "%s: ", blockname
);
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
)
538 printe( 1, "%s: seeking in the image failed (%s)\n",
539 blockname
, strerror( errno
) );
544 for( lb
= 0, progress( 0 ); lb
< block
.size
; lb
++ ) {
545 /* Read one sector (possibly decrypted) */
547 rc
= ( dvdcss_read( dvdcss
, buffer
, 1, read_flags
) != 1 );
549 rc
= ( DVDReadBlocks( file
, lb
, 1, buffer
) == (ssize_t
)(-1) );
553 printe( 1, "%s: reading sector %d failed\n", blockname
, lb
);
559 rc
= ( write( img
, (void *)buffer
, DVD_VIDEO_LB_LEN
) == (ssize_t
)(-1) );
562 printe( 1, "%s: writing sector %d failed (%s)\n",
563 blockname
, lb
, strerror( errno
) );
567 progress( (int)(lb
*100/block
.size
) );
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
);
583 /* Keep a percentage indicator at the end of the line */
584 static int progress( const int perc
)
587 if( perc
>= 101 ) { /* abort */
592 else if( perc
< 0 ) { /* init */
598 else if( perc
== 100 ) { /* finish */
601 printe( 3, "\b\b\b\b100%%\n" );
603 printe( 2, "done\n" );
606 else if( perc
!= last
) { /* update */
608 if( verbosity
> 2 ) {
609 printe( 2, "\b\b\b\b% 3d%%", perc
);
617 /* Print on stdout/stderr depending on the verbosity level */
618 int printe( const char level
, const char *format
, ... )
622 FILE *stream
= stdout
;
625 if( level
> verbosity
)
628 va_start( arg
, format
);
630 fprintf( stream
, "%s: ", progname
);
631 ret
= vfprintf( stream
, format
, arg
);