dvdimgdecss.c: dvdsize(): new function
[cdimgtools.git] / dvdimgdecss.c
blob9afd92b0e07f761e3f087f875be4eb89af6095b1
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 int rc, status = EX_SUCCESS;
117 setvbuf( stdout, NULL, _IOLBF, BUFSIZ );
119 /* Options */
120 char fcreate = 0, dvdcssread = 0;
121 extern int optind;
122 while( (rc = getopt( argc, argv, "fc" )) != -1 )
123 switch( (char)rc ) {
124 case 'f':
125 fcreate = 1;
126 break;
127 case 'c':
128 dvdcssread = 1;
129 break;
130 case '?':
131 default:
132 usage( );
133 exit( EX_USAGE );
135 argc -= optind;
136 argv += optind;
138 /* Command line args */
139 if( argc < 1 || argc > 2 ) {
140 fprintf( stderr, "%s: syntax error\n", progname );
141 usage( );
142 exit( EX_USAGE );
144 dvdfile = argv[0];
145 if( argc == 2 ) imgfile = argv[1];
146 if( ! imgfile ) dvdcssread = 0; /* libdvdcss not used if no image written */
148 /* Open the DVD */
149 dvd = DVDOpen( dvdfile );
150 if( dvd == NULL ) {
151 fprintf( stderr, "%s: opening of the DVD (%s) failed\n",
152 progname, dvdfile );
153 exit( status | EX_OPEN );
155 if( dvdcssread ) {
156 dvdcss = dvdcss_open( dvdfile );
157 if( dvdcss == NULL ) {
158 fprintf( stderr, "%s: opening of the DVD (%s) with libdvdcss failed\n",
159 progname, dvdfile );
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 */
168 if( imgfile ) {
169 img = open( imgfile, fcreate ? O_RDWR | O_CREAT : O_RDWR,
170 S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH );
171 if( img < 0 ) {
172 fprintf( stderr, "%s: opening of the image file (%s) failed (%s)\n",
173 progname, imgfile, strerror( errno ) );
174 status |= EX_OPEN;
176 else {
177 printf( "\n" );
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 ) );
182 status |= EX_IO;
187 /* Close DVD */
188 DVDClose( dvd );
189 if( dvdcss && (dvdcss_close( dvdcss ) < 0) ) {
190 fprintf( stderr, "closing of the DVD with libdvdcss failed\n" );
191 status |= EX_IO;
193 exit( status );
196 /* Return the size in sectors (whether it is a file or a special device) */
197 static int dvdsize( const char *dvdfile )
199 off_t size;
200 struct stat buf;
201 int dvd, rc;
203 rc = stat( dvdfile, &buf );
204 if( rc < 0 ) {
205 fprintf( stderr, "%s: stat DVD (%s) failed (%s)\n",
206 progname, dvdfile, strerror( errno ) );
207 return -1;
210 if( !buf.st_rdev )
211 size = buf.st_size;
212 else {
213 dvd = open( dvdfile, O_RDONLY );
214 if( dvd < 0 ) {
215 fprintf( stderr, "%s: opening the DVD (%s) failed (%s)\n",
216 progname, dvdfile, strerror( errno ) );
217 return -1;
219 size = lseek( dvd, 0, SEEK_END );
220 if( size < 0 ) {
221 fprintf( stderr, "%s: seeking at the end of the DVD failed (%s)\n",
222 progname, strerror( errno ) );
223 return -1;
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;
241 block_t block;
242 int count = 0, title, i, start;
244 /* Video Manager */
245 title = 0;
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 );
256 /* Titles */
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 )
261 && count++;
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 );
284 return status;
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;
291 (*block).start = 0;
292 (*block).size = -1;
294 sector = UDFFindFile( dvd, filename, &size );
295 if( sector ) {
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 );
304 return 1;
307 return 0;
310 /* Remove from blocks the block sectors of all title/domains */
311 static int removetitles( blockl_t blocks, titleblocks_t titles[] )
313 block_t *block;
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 );
322 if( rc == 0 ) {
323 fprintf( stderr, "%s: Title %02d %s: block mismatch\n",
324 progname, title, domainname( domain ) );
325 status |= EX_MISMATCH;
327 if( rc == -1 ) {
328 status |= EX_MEM;
329 break;
333 return status;
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 )
342 blockl_t cur, new;
344 if( block.size < 0)
345 return 1;
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 ) );
354 if( ! new ) {
355 fprintf( stderr, "%s: memory allocation failed\n", progname );
356 return -1;
358 /* Make a hole */
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;
364 cur->tail = new;
365 /* Remove empty blocks */
366 if( new->block.size == 0 ) {
367 cur->tail = new->tail;
368 free( new );
370 if( cur->block.size == 0 ) {
371 new = cur->tail;
372 *cur = *cur->tail;
373 free( new );
375 return 1;
378 return 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[] )
385 dvd_file_t *file;
386 block_t *block;
387 char blockname[24];
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 ) );
398 /* Checks */
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 );
416 else
417 rc = copyblock( file, dvdcss, img, *block, blockname );
418 status |= rc;
419 status &= ~EX_NOP;
420 if( rc != EX_SUCCESS )
421 fprintf( stderr, "%s: %s: partial decryption\n",
422 progname, blockname );
424 DVDCloseFile( file );
427 return status;
430 static int copyblocks( dvdcss_t dvdcss, int img, blockl_t blocks )
432 char blockname[24];
433 int status = EX_SUCCESS;
435 if( dvdcss == NULL ) {
436 fprintf( stderr, "%s: cannot copy ordinary blocks without libdvdcss\n",
437 progname );
438 status |= EX_IO;
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 );
448 if( status )
449 fprintf( stderr, "%s: error while copying ordinary blocks\n", progname );
450 return status;
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 );
470 return status;
472 if( file == NULL && dvdcss == NULL ) {
473 printf( "%s: skip\n", blockname );
474 return status;
476 if( block.size == 0 ) {
477 printf( "%s: null\n", blockname );
478 return status;
481 /* Seek in the input */
482 if( dvdcss ) {
483 rc = dvdcss_seek( dvdcss, block.start, seek_flags );
484 if( rc < 0 ) {
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 ) );
488 status |= EX_IO;
492 printf( "%s: ", blockname );
493 progress( -1 );
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 )
497 == (off_t)(-1) );
498 if( rc ) {
499 progress( 101 );
500 fprintf( stderr, "%s: %s: seeking in the image failed (%s)\n",
501 progname, blockname, strerror( errno ) );
502 status |= EX_IO;
503 return status;
506 for( lb = 0, progress( 0 ); lb < block.size; lb++ ) {
507 /* Read one sector (possibly decrypted) */
508 if( dvdcss )
509 rc = ( dvdcss_read( dvdcss, buffer, 1, read_flags ) != 1 );
510 else
511 rc = ( DVDReadBlocks( file, lb, 1, buffer ) == (ssize_t)(-1) );
512 if( rc ) {
513 progress( 101 );
514 if( file )
515 fprintf( stderr, "%s: %s: reading sector %d failed\n",
516 progname, blockname, lb );
517 status |= EX_IO;
518 return status;
521 /* Write the data */
522 rc = ( write( img, (void *)buffer, DVD_VIDEO_LB_LEN ) == (ssize_t)(-1) );
523 if( rc ) {
524 progress( 101 );
525 fprintf( stderr, "%s: %s: writing sector %d failed (%s)\n",
526 progname, blockname, lb, strerror( errno ) );
527 status |= EX_IO;
528 return status;
530 progress( (int)(lb*100/block.size) );
533 progress( 100 );
534 return status;
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 );
543 return NULL;
546 /* Keep a percentage indicator at the end of the line */
547 static int progress( const int perc )
549 static int last = 0;
550 if( perc >= 101 ) { /* abort */
551 last = 0;
552 printf( "\n" );
553 fflush( stdout );
555 else if( perc < 0 ) { /* init */
556 last = 0;
557 printf( " %%" );
558 fflush( stdout );
560 else if( perc == 100 ) { /* finish */
561 last = 0;
562 printf( "\b\b\b\b100%%\n" );
563 fflush( stdout );
565 else if( perc != last ) { /* update */
566 last = perc;
567 printf( "\b\b\b\b% 3d%%", perc );
568 fflush( stdout );
569 return 1;
571 return 0;