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 int rc
, status
= EX_SUCCESS
;
117 setvbuf( stdout
, NULL
, _IOLBF
, BUFSIZ
);
120 char fcreate
= 0, dvdcssread
= 0;
122 while( (rc
= getopt( argc
, argv
, "fc" )) != -1 )
138 /* Command line args */
139 if( argc
< 1 || argc
> 2 ) {
140 fprintf( stderr
, "%s: syntax error\n", progname
);
145 if( argc
== 2 ) imgfile
= argv
[1];
146 if( ! imgfile
) dvdcssread
= 0; /* libdvdcss not used if no image written */
149 dvd
= DVDOpen( dvdfile
);
151 fprintf( stderr
, "%s: opening of the DVD (%s) failed\n",
153 exit( status
| EX_OPEN
);
156 dvdcss
= dvdcss_open( dvdfile
);
157 if( dvdcss
== NULL
) {
158 fprintf( stderr
, "%s: opening of the DVD (%s) with libdvdcss failed\n",
160 exit( status
| EX_OPEN
);
164 /* Search the DVD for the positions of the title files */
165 status
|= savetitleblocks( dvd
, &titles
);
167 /* Check & Decrypt & Write */
169 img
= open( imgfile
, fcreate
? O_RDWR
| O_CREAT
: O_RDWR
,
170 S_IRUSR
|S_IWUSR
|S_IRGRP
|S_IWGRP
|S_IROTH
|S_IWOTH
);
172 fprintf( stderr
, "%s: opening of the image file (%s) failed (%s)\n",
173 progname
, imgfile
, strerror( errno
) );
178 status
|= decrypttitles( dvd
, dvdcss
, img
, titles
);
179 if( close( img
) < 0 ) {
180 fprintf( stderr
, "%s: closing of the image file failed (%s)\n",
181 progname
, strerror( errno
) );
189 if( dvdcss
&& (dvdcss_close( dvdcss
) < 0) ) {
190 fprintf( stderr
, "closing of the DVD with libdvdcss failed\n" );
196 /* Return the size in sectors (whether it is a file or a special device) */
197 static int dvdsize( const char *dvdfile
)
203 rc
= stat( dvdfile
, &buf
);
205 fprintf( stderr
, "%s: stat DVD (%s) failed (%s)\n",
206 progname
, dvdfile
, strerror( errno
) );
213 dvd
= open( dvdfile
, O_RDONLY
);
215 fprintf( stderr
, "%s: opening the DVD (%s) failed (%s)\n",
216 progname
, dvdfile
, strerror( errno
) );
219 size
= lseek( dvd
, 0, SEEK_END
);
221 fprintf( stderr
, "%s: seeking at the end of the DVD failed (%s)\n",
222 progname
, strerror( errno
) );
225 if( close( dvd
) < 0 )
226 fprintf( stderr
, "%s: closing of the DVD failed (%s)\n",
227 progname
, strerror( errno
) );
230 if( size
% DVD_VIDEO_LB_LEN
)
231 fprintf( stderr
, "%s: DVD size is not a block multiple\n", progname
);
232 return size
/ DVD_VIDEO_LB_LEN
;
235 /* Save the sector positions of the title/domain files */
236 static int savetitleblocks( dvd_reader_t
*dvd
, titleblocks_t (*titles
)[TITLE_MAX
] )
238 int status
= EX_SUCCESS
;
239 char filename
[32]; /* MAX_UDF_FILE_NAME_LEN too much */
240 titleblocks_t
*tblocks
;
242 int count
= 0, title
, i
, start
;
246 tblocks
= &(*titles
)[title
];
247 sprintf( filename
, "/VIDEO_TS/VIDEO_TS.%s", "IFO" );
248 fileblock( dvd
, filename
, &tblocks
->ifo
)
249 || printf( "%s: WARNING %s not found\n", progname
, filename
);
250 sprintf( filename
, "/VIDEO_TS/VIDEO_TS.%s", "VOB" );
251 fileblock( dvd
, filename
, &tblocks
->menu
);
252 fileblock( dvd
, "/VIDEO_TS/invalid_name/", &tblocks
->vob
);
253 sprintf( filename
, "/VIDEO_TS/VIDEO_TS.%s", "BUP" );
254 fileblock( dvd
, filename
, &tblocks
->bup
);
257 for( title
= 1; title
< TITLE_MAX
; title
++ ) {
258 tblocks
= &(*titles
)[title
];
259 sprintf( filename
, "/VIDEO_TS/VTS_%02d_%d.%s", title
, 0, "IFO" );
260 fileblock( dvd
, filename
, &tblocks
->ifo
)
262 sprintf( filename
, "/VIDEO_TS/VTS_%02d_%d.%s", title
, 0, "VOB" );
263 fileblock( dvd
, filename
, &tblocks
->menu
);
264 sprintf( filename
, "/VIDEO_TS/VTS_%02d_%d.%s", title
, 1, "VOB" );
265 fileblock( dvd
, filename
, &tblocks
->vob
);
266 for( i
= 2, start
= tblocks
->vob
.start
+tblocks
->vob
.size
; i
< 10; i
++ ) {
267 /* Title VOBs may be split into several files */
268 sprintf( filename
, "/VIDEO_TS/VTS_%02d_%d.%s", title
, i
, "VOB" );
269 if( fileblock( dvd
, filename
, &block
) ) {
270 tblocks
->vob
.size
+= block
.size
;
271 if( block
.start
!= start
) {
272 fprintf( stderr
, "%s: WARNING whole in title %d before part %d\n",
273 progname
, title
, i
);
274 status
|= EX_MISMATCH
;
276 start
= block
.start
+block
.size
;
279 sprintf( filename
, "/VIDEO_TS/VTS_%02d_%d.%s", title
, 0, "BUP" );
280 fileblock( dvd
, filename
, &tblocks
->bup
);
283 printf( "%s: %d titles found\n", progname
, count
);
287 /* Record the sector range over which a file spans */
288 static int fileblock( dvd_reader_t
*dvd
, char *filename
, block_t
*block
)
290 uint32_t sector
, size
;
294 sector
= UDFFindFile( dvd
, filename
, &size
);
296 if( size
% DVD_VIDEO_LB_LEN
)
297 fprintf( stderr
, "%s: WARNING size of %s is not a block multiple\n",
298 progname
, filename
);
299 size
/= DVD_VIDEO_LB_LEN
;
300 (*block
).start
= sector
;
301 (*block
).size
= size
;
302 printf( "%s: %s at 0x%08x-0x%08x\n",
303 progname
, filename
, sector
, sector
+size
);
310 /* Remove from blocks the block sectors of all title/domains */
311 static int removetitles( blockl_t blocks
, titleblocks_t titles
[] )
314 dvd_read_domain_t domain
;
315 int title
, i
, rc
, status
= EX_SUCCESS
;
317 for( title
= 0; title
< TITLE_MAX
; title
++ )
318 for( i
= 0; i
< DOMAIN_MAX
; i
++ ) {
319 domain
= dvd_read_domains
[i
];
320 block
= domainblock( &titles
[title
], domain
);
321 rc
= removeblock( blocks
, *block
);
323 fprintf( stderr
, "%s: Title %02d %s: block mismatch\n",
324 progname
, title
, domainname( domain
) );
325 status
|= EX_MISMATCH
;
336 /* Remove a block from a block list (if it is contained in a block of the list) */
337 /* Inexistent/invalid blocks are ignored. */
338 /* If the list is increasing the result is also increasing. */
339 /* blocks must contain freeable memory (except for the first one). */
340 static int removeblock( blockl_t blocks
, const block_t block
)
347 for( cur
= blocks
; cur
!= NULL
; cur
= cur
->tail
)
349 ( cur
->block
.start
<= block
.start
350 && cur
->block
.start
+cur
->block
.size
>= block
.start
+block
.size
)
352 /* Allocate a new node */
353 new = malloc( sizeof( struct blockl
) );
355 fprintf( stderr
, "%s: memory allocation failed\n", progname
);
359 new->block
.start
= block
.start
+ block
.size
;
360 new->block
.size
= cur
->block
.start
+ cur
->block
.size
- new->block
.start
;
361 cur
->block
.size
= block
.start
- cur
->block
.start
;
362 /* Insert the new block */
363 new->tail
= cur
->tail
;
365 /* Remove empty blocks */
366 if( new->block
.size
== 0 ) {
367 cur
->tail
= new->tail
;
370 if( cur
->block
.size
== 0 ) {
381 /* Iterate over titles and domains and check consistency and copy blocks
382 * corresponding to a VOB domain at the right position */
383 static int decrypttitles( dvd_reader_t
*dvd
, dvdcss_t dvdcss
, int img
, titleblocks_t titles
[] )
388 dvd_read_domain_t domain
;
389 int title
, i
, rc
, status
= EX_NOP
;
391 for( title
= 0; title
< TITLE_MAX
; title
++ )
392 for( i
= 0; i
< DOMAIN_MAX
; i
++ ) {
393 domain
= dvd_read_domains
[i
];
394 block
= domainblock( &titles
[title
], domain
);
395 file
= openfile( dvd
, title
, domain
);
396 snprintf( blockname
, 24, "Title %02d %s", title
, domainname( domain
) );
399 if( !!file
!= !!(block
->size
>= 0) ) {
400 fprintf( stderr
, "%s: ERROR %s: domain mismatch\n",
401 progname
, blockname
);
402 status
|= EX_MISMATCH
;
404 if( ! file
) continue;
405 if( domain
== DVD_READ_INFO_FILE
)
406 printf( "TITLE %02d\n", title
);
407 if( DVDFileSize( file
) != (ssize_t
)(block
->size
) ) {
408 fprintf( stderr
, "%s: ERROR %s: size mismatch %zd != %d\n",
409 progname
, blockname
, DVDFileSize( file
), block
->size
);
410 status
|= EX_MISMATCH
;
413 /* Decrypt VOBs only */
414 if( domain
!= DVD_READ_MENU_VOBS
&& domain
!= DVD_READ_TITLE_VOBS
)
415 rc
= copyblock( NULL
, dvdcss
, img
, *block
, blockname
);
417 rc
= copyblock( file
, dvdcss
, img
, *block
, blockname
);
420 if( rc
!= EX_SUCCESS
)
421 fprintf( stderr
, "%s: %s: partial decryption\n",
422 progname
, blockname
);
424 DVDCloseFile( file
);
430 static int copyblocks( dvdcss_t dvdcss
, int img
, blockl_t blocks
)
433 int status
= EX_SUCCESS
;
435 if( dvdcss
== NULL
) {
436 fprintf( stderr
, "%s: cannot copy ordinary blocks without libdvdcss\n",
441 printf( "BLOCKS\n" );
442 for( ; blocks
!= NULL
; blocks
= blocks
->tail
) {
443 snprintf( blockname
, 24, "Block %08x-%08x",
444 blocks
->block
.start
, blocks
->block
.start
+blocks
->block
.size
);
445 status
|= copyblock( NULL
, dvdcss
, img
, blocks
->block
, blockname
);
449 fprintf( stderr
, "%s: error while copying ordinary blocks\n", progname
);
453 /* If file is not NULL, copy/decrypt a title/domain, using libdvdcss for
454 * reading if dvdcss is not NULL, using libdvdread otherwise. */
455 /* If file is NULL, copy an ordinary block (ignoring title and domain). */
456 static int copyblock( dvd_file_t
*file
, dvdcss_t dvdcss
, int img
,
457 block_t block
, const char *blockname
)
459 int lb
, rc
, status
= EX_SUCCESS
;
460 int seek_flags
= file
? DVDCSS_SEEK_KEY
: DVDCSS_NOFLAGS
;
461 int read_flags
= file
? DVDCSS_READ_DECRYPT
: DVDCSS_NOFLAGS
;
462 /* Aligned read buffer */
463 static unsigned char data
[DVD_VIDEO_LB_LEN
* 2];
464 unsigned char *buffer
=
465 data
+ DVD_VIDEO_LB_LEN
466 - ((long int)data
& (DVD_VIDEO_LB_LEN
-1));
468 if( block
.size
< 0 ) {
469 printf( "%s: inva\n", blockname
);
472 if( file
== NULL
&& dvdcss
== NULL
) {
473 printf( "%s: skip\n", blockname
);
476 if( block
.size
== 0 ) {
477 printf( "%s: null\n", blockname
);
481 /* Seek in the input */
483 rc
= dvdcss_seek( dvdcss
, block
.start
, seek_flags
);
485 fprintf( stderr
, "%s: %s: seeking in the input (dvdcss%s) failed (%s)\n",
486 progname
, blockname
, seek_flags
& DVDCSS_SEEK_KEY
? " key" : "",
487 dvdcss_error( dvdcss
) );
492 printf( "%s: ", blockname
);
495 /* Seek to to the right position in the output image */
496 rc
= ( lseek( img
, (off_t
)(block
.start
) * DVD_VIDEO_LB_LEN
, SEEK_SET
)
500 fprintf( stderr
, "%s: %s: seeking in the image failed (%s)\n",
501 progname
, blockname
, strerror( errno
) );
506 for( lb
= 0, progress( 0 ); lb
< block
.size
; lb
++ ) {
507 /* Read one sector (possibly decrypted) */
509 rc
= ( dvdcss_read( dvdcss
, buffer
, 1, read_flags
) != 1 );
511 rc
= ( DVDReadBlocks( file
, lb
, 1, buffer
) == (ssize_t
)(-1) );
515 fprintf( stderr
, "%s: %s: reading sector %d failed\n",
516 progname
, blockname
, lb
);
522 rc
= ( write( img
, (void *)buffer
, DVD_VIDEO_LB_LEN
) == (ssize_t
)(-1) );
525 fprintf( stderr
, "%s: %s: writing sector %d failed (%s)\n",
526 progname
, blockname
, lb
, strerror( errno
) );
530 progress( (int)(lb
*100/block
.size
) );
537 /* Test for file existence before open (to silence libdvdnav) */
538 dvd_file_t
*openfile( dvd_reader_t
*dvd
, int title
, dvd_read_domain_t domain
)
540 static dvd_stat_t stat
;
541 if( ! DVDFileStat( dvd
, title
, domain
, &stat
) )
542 return DVDOpenFile( dvd
, title
, domain
);
546 /* Keep a percentage indicator at the end of the line */
547 static int progress( const int perc
)
550 if( perc
>= 101 ) { /* abort */
555 else if( perc
< 0 ) { /* init */
560 else if( perc
== 100 ) { /* finish */
562 printf( "\b\b\b\b100%%\n" );
565 else if( perc
!= last
) { /* update */
567 printf( "\b\b\b\b% 3d%%", perc
);