misc/cssdec.c: check write error and report
[cdimgtools:cdimgtools.git] / misc / cssdec.c
1 /* cssdec.c - simple css descrambling program using libdvdcss
2  * Copyright © 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  */
7
8 #include <stdlib.h>
9 #include <stdarg.h>
10 #include <stdio.h>
11 #include <limits.h>
12 #include <getopt.h>
13 #include <errno.h>
14 #include <string.h>
15
16 #include <dvdcss/dvdcss.h>
17
18 #define psz_progname "cssdec"
19 #ifndef DEFAULT_VERBOSITY
20 #       define DEFAULT_VERBOSITY 1
21 #endif
22 char c_verbosity = DEFAULT_VERBOSITY;
23 #define READ_ERROR 1<<0
24 #define READ_EOF 1<<1
25 #define SCRAMBLED 1<<2
26 #define DECRYPTED 1<<3
27 #define KEY_CHANGED 1<<4
28 #define FAILED_DECRYPTION 1<<5
29
30 static int  readsector ( dvdcss_t, unsigned char *, const int );
31 static int  isscrambled( const unsigned char * );
32 static int  dumpsector ( unsigned char * );
33 static int  printe     ( const char, const char *, ... );
34
35 static void usage( )
36 {
37         printf( "Usage:\n" );
38         printf( "\t%s [-v] [-q] [-e] <file> [<start_sector> [<end_sector>]]\n",
39           psz_progname );
40 }
41
42 int main( int argc, char *argv[] )
43 {
44         char          *psz_dvdfile;
45         dvdcss_t       dvdcss;
46         unsigned char  p_data[ DVDCSS_BLOCK_SIZE * 2 ];
47         unsigned char *p_buffer;
48         unsigned int   i_sector = 0, i_end = INT_MAX;
49         int            i_keys = 0, i_unread = 0, i_scrambled = 0, i_skipped = 0,
50                        i_undecrypted = 0, i_unwritten = 0;
51         int            i_ret;
52
53         /* Options */
54         char b_noeof = 0;
55         extern int optind;
56         while( (i_ret = getopt( argc, argv, "qve" )) != -1 )
57                 switch( (char)i_ret )
58                 {
59                 case 'q':
60                         c_verbosity--;
61                         break;
62                 case 'v':
63                         c_verbosity++;
64                         break;
65                 case 'e':
66                         b_noeof = 1;
67                         break;
68                 case '?':
69                 default:
70                         usage( );
71                         exit( -1 );
72                 }
73         argc -= optind;
74         argv += optind;
75
76         /* Command line args */
77         if( argc < 1 || argc > 3 )
78         {
79                 printe( 1, "syntax error" );
80                 usage( );
81                 exit( -1 );
82         }
83         psz_dvdfile = argv[0];
84         if( argc >= 2 ) i_sector = atoi( argv[1] );
85         if( argc >= 3 ) i_end = atoi( argv[2] );
86
87         /* Initialize libdvdcss */
88         printe( 2, "using libdvdcss version %s", dvdcss_interface_2 );
89         dvdcss = dvdcss_open( psz_dvdfile );
90         if( dvdcss == NULL )
91         {
92                 printe( 1, "opening of the DVD (%s) failed", psz_dvdfile );
93                 exit( 127 );
94         }
95
96         /* Align our read buffer */
97         p_buffer = p_data + DVDCSS_BLOCK_SIZE
98                           - ((long int)p_data & (DVDCSS_BLOCK_SIZE-1));
99
100         for( ; i_sector < i_end; i_sector++ )
101         {
102                 /* Read decrypted */
103                 i_ret = readsector( dvdcss, p_buffer, i_sector );
104
105                 /* Check & Count */
106                 if( i_ret & READ_EOF )
107                 {
108                         printe( 2, "stop reading before sector %d", i_sector );
109                         break;
110                 }
111                 if( i_ret & READ_ERROR )
112                 {
113                         printe( 3, "sect %d: skipping processing", i_sector );
114                         i_unread++;
115                         continue;
116                 }
117                 if( i_ret & SCRAMBLED )
118                 {
119                         i_scrambled++;
120                         if( i_ret & KEY_CHANGED ) i_keys++;
121                         if( ! (i_ret & DECRYPTED) )
122                         {
123                                 if( i_ret & KEY_CHANGED )
124                                         i_undecrypted++;
125                                 else
126                                         i_skipped++;
127                         }
128                 }
129
130                 /* Process the sector */
131                 if( ! dumpsector( p_buffer ) ) {
132                         printe( 1, "sect %d: writing failed; aborting", i_sector );
133                         i_unwritten++;
134                         break;
135                 };
136         }
137
138         /* Close the dvdcss device */
139         i_ret = dvdcss_close( dvdcss );
140         if( i_ret < 0 ) printe( 1, "closing of the DVD failed" );
141
142         /* Summary & Return status */
143         printe( 2, "summary of processed sectors:" );
144         if( i_unwritten ) printe( 2, "error while writing" );
145         printe( 2, "%d unread sectors", i_unread );
146         printe( 2, "%d undecrypted sectors", i_undecrypted );
147         printe( 2, "%d key changes", i_keys );
148         printe( 2, "%d (seemingly) scrambled sectors without a key", i_skipped );
149         printe( 2, "%d scrambled sectors", i_scrambled );
150         i_ret = 0;
151         if( i_unread || i_unwritten || (b_noeof && i_sector < i_end) ) i_ret |= 1<<4;
152         if( i_undecrypted ) i_ret |= 1;
153         exit( i_ret );
154 }
155
156 /* Read a sector; read decrypted again if it seems crypted;
157  * eventually try to get a key */
158 static int readsector( dvdcss_t dvdcss, unsigned char *p_buffer, const int i_sector )
159 {
160         int i_ret, i_return = 0;
161
162         /* Seek at sector i_sector and read one sector */
163         i_ret = dvdcss_seek( dvdcss, i_sector, DVDCSS_NOFLAGS );
164         if( i_ret < 0 )
165         {
166                 printe( 1, "sect %d: seek failed (%s)", i_sector, dvdcss_error( dvdcss ) );
167                 return i_return | READ_ERROR;
168         }
169         i_ret = dvdcss_read( dvdcss, p_buffer, 1, DVDCSS_NOFLAGS );
170         if( i_ret < 0 )
171         {
172                 printe( 1, "sect %d: read failed (%s)", i_sector, dvdcss_error( dvdcss ) );
173                 return i_return | READ_ERROR;
174         }
175         if( i_ret == 0 )
176         {
177                 printe( 1, "sect %d: EOF", i_sector );
178                 return i_return | READ_EOF;
179         }
180
181         if( ! isscrambled( p_buffer ) /* Check if sector is encrypted */ )
182                 printe( 3, "sect %d: not crypted", i_sector );
183         else
184         {
185                 printe( 3, "sect %d: crypted", i_sector );
186                 i_return |= SCRAMBLED;
187
188                 /* Seek at sector i_sector and try to decrypt sector */
189                 i_ret = dvdcss_seek( dvdcss, i_sector, DVDCSS_NOFLAGS );
190                 if( i_ret < 0 )
191                 {
192                         printe( 1, "sect %d: seek failed (%s)",
193                           i_sector, dvdcss_error( dvdcss ) );
194                         return i_return;
195                 }
196                 i_ret = dvdcss_read( dvdcss, p_buffer, 1, DVDCSS_READ_DECRYPT );
197                   /* Warning: A failure to decrypt is not considered an error in
198                    * libdvdcss 1.2.12 */
199                 if( i_ret != 1 )
200                 {
201                         printe( 2, "sect %d: read (decrypted) failed (%s)",
202                           i_sector, dvdcss_error( dvdcss ) );
203                         return i_return;
204                 }
205
206                 if( isscrambled( p_buffer ) /* Check if sector is still encrypted */ )
207                 {
208                         /* Seek at sector i_sector and get a new key */
209                         i_ret = dvdcss_seek( dvdcss, i_sector, DVDCSS_SEEK_KEY );
210                         if( i_ret < 0 )
211                         {
212                                 printe( 2, "sect %d: seek (key) failed (%s); skipping decryption",
213                                   i_sector, dvdcss_error( dvdcss ) );
214                                 return i_return;
215                         }
216                         printe( 2, "sect %d: new key", i_sector );
217                         i_return |= KEY_CHANGED;
218
219                         /* Re-try to decrypt sector */
220                         i_ret = dvdcss_read( dvdcss, p_buffer, 1, DVDCSS_READ_DECRYPT );
221                         if( i_ret != 1 )
222                         {
223                                 printe( 2, "sect %d: read (decrypted) failed (%s); aborting decryption",
224                                   i_sector, dvdcss_error( dvdcss ) );
225                                 return i_return;
226                         }
227                 }
228
229                 if( isscrambled( p_buffer ) /* Check if the decryption really succeeded */ )
230                 {
231                         /* Probably a bug in libdvdcss not to have given an error earlier */
232                         printe( 1, "sect %d: still apparently crypted after decryption",
233                           i_sector );
234                         i_return |= FAILED_DECRYPTION;
235                 }
236                 else
237                 {
238                         printe( 3, "sect %d: decrypted", i_sector );
239                         i_return |= DECRYPTED;
240                 }
241         }
242
243         return i_return;
244 }
245
246 /* Check if a sector is scrambled */
247 static int isscrambled( const unsigned char *p_buffer )
248 {
249         return p_buffer[ 0x14 ] & 0x30;
250 }
251
252 /* Dump the sector on stdout */
253 static int dumpsector( unsigned char *p_buffer )
254 {
255         size_t n;
256         n = fwrite( (void *)p_buffer, DVDCSS_BLOCK_SIZE, 1, stdout );
257         if( ferror( stdout ) )
258         {
259                 printe( 1, "write error (%s)", strerror( errno ) );
260                 clearerr( stdout );
261                 return 0;
262         }
263         return (n == 1);
264 }
265
266 /* Print a line on stderr preceded by the program name */
267 int printe( const char c_level, const char *psz_format, ... )
268 {
269         va_list arg;
270         int i_ret;
271         if( c_level > c_verbosity ) return 1;
272
273         va_start( arg, psz_format );
274         fprintf( stderr, "%s: ", psz_progname );
275         i_ret = vfprintf( stderr, psz_format, arg );
276         va_end( arg );
277         fprintf( stderr, "\n" );
278         return i_ret;
279 }