]> git.sesse.net Git - vlc/blobdiff - extras/libdvdcss/libdvdcss.c
*Merged hh patch in libdvdcss from main tree.
[vlc] / extras / libdvdcss / libdvdcss.c
index 12cd903726b753efdc1b1e59a0d34fd2ecabcd26..05dd58743180fa60636b8bf150a713f22dfac259 100644 (file)
@@ -2,10 +2,11 @@
  * libdvdcss.c: DVD reading library.
  *****************************************************************************
  * Copyright (C) 1998-2001 VideoLAN
- * $Id: libdvdcss.c,v 1.10 2001/07/27 01:05:17 sam Exp $
+ * $Id: libdvdcss.c,v 1.30 2002/01/23 03:15:31 stef Exp $
  *
  * Authors: Stéphane Borel <stef@via.ecp.fr>
  *          Samuel Hocevar <sam@zoy.org>
+ *          Håkan Hjort <d95hjort@dtek.chalmers.se>
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
 /*****************************************************************************
  * Preamble
  *****************************************************************************/
-#include "defs.h"
-
 #include <stdio.h>
 #include <stdlib.h>
+#include <string.h>
 #include <sys/types.h>
 #include <sys/stat.h>
 #include <fcntl.h>
 
+#include <videolan/vlc.h>
+
 #ifdef HAVE_UNISTD_H
 #   include <unistd.h>
 #endif
 
 #if defined( WIN32 )
-#   include <io.h>
-#   include "input_iovec.h"
+#   include <io.h>                                                 /* read() */
 #else
 #   include <sys/uio.h>                                      /* struct iovec */
 #endif
 
-#include "config.h"
-#include "common.h"
+#if defined( WIN32 )
+#   include "input_iovec.h"
+#endif
 
 #include "videolan/dvdcss.h"
 #include "libdvdcss.h"
@@ -56,8 +58,6 @@
  *****************************************************************************/
 static int _dvdcss_open  ( dvdcss_handle, char *psz_target );
 static int _dvdcss_close ( dvdcss_handle );
-static int _dvdcss_seek  ( dvdcss_handle, int i_blocks );
-static int _dvdcss_read  ( dvdcss_handle, void *p_buffer, int i_blocks );
 static int _dvdcss_readv ( dvdcss_handle, struct iovec *p_iovec, int i_blocks );
 
 /*****************************************************************************
@@ -70,14 +70,23 @@ static int _win32_dvdcss_aopen  ( char c_drive, dvdcss_handle dvdcss );
 static int _win32_dvdcss_aclose ( int i_fd );
 static int _win32_dvdcss_aseek  ( int i_fd, int i_blocks, int i_method );
 static int _win32_dvdcss_aread  ( int i_fd, void *p_data, int i_blocks );
+#else
+static int _dvdcss_raw_open     ( dvdcss_handle, char *psz_target );
 #endif
 
 /*****************************************************************************
  * dvdcss_open: initialize library, open a DVD device, crack CSS key
  *****************************************************************************/
-extern dvdcss_handle dvdcss_open ( char *psz_target, int i_flags )
+extern dvdcss_handle dvdcss_open ( char *psz_target )
 {
-    int i_ret;
+    struct stat fileinfo;
+    int         i_ret;
+
+    char *psz_method = getenv( "DVDCSS_METHOD" );
+    char *psz_verbose = getenv( "DVDCSS_VERBOSE" );
+#ifndef WIN32
+    char *psz_raw_device = getenv( "DVDCSS_RAW_DEVICE" );
+#endif
 
     dvdcss_handle dvdcss;
 
@@ -85,20 +94,63 @@ extern dvdcss_handle dvdcss_open ( char *psz_target, int i_flags )
     dvdcss = malloc( sizeof( struct dvdcss_s ) );
     if( dvdcss == NULL )
     {
-        if( ! (i_flags & DVDCSS_INIT_QUIET) )
-        {
-            DVDCSS_ERROR( "could not initialize library" );
-        }
-
         return NULL;
     }
 
-    /* Initialize structure */
+    /* Initialize structure with default values */
     dvdcss->p_titles = NULL;
-    dvdcss->b_debug = i_flags & DVDCSS_INIT_DEBUG;
-    dvdcss->b_errors = !(i_flags & DVDCSS_INIT_QUIET);
     dvdcss->psz_error = "no error";
+    dvdcss->i_method = DVDCSS_METHOD_TITLE;
+    dvdcss->b_debug = 0;
+    dvdcss->b_errors = 1;
+
+    /* Find method from DVDCSS_METHOD environment variable */
+    if( psz_method != NULL )
+    {
+        if( !strncmp( psz_method, "key", 4 ) )
+        {
+            dvdcss->i_method = DVDCSS_METHOD_KEY;
+        }
+        else if( !strncmp( psz_method, "disc", 5 ) )
+        {
+            dvdcss->i_method = DVDCSS_METHOD_DISC;
+        }
+        else if( !strncmp( psz_method, "title", 5 ) )
+        {
+            dvdcss->i_method = DVDCSS_METHOD_TITLE;
+        }
+        else
+        {
+            _dvdcss_error( dvdcss, "unknown decrypt method, please choose "
+                                   "from 'title', 'key' or 'disc'" );
+            free( dvdcss );
+            return NULL;
+        }
+    }
+
+    /* Find verbosity from DVDCSS_VERBOSE environment variable */
+    if( psz_verbose != NULL )
+    {
+        switch( atoi( psz_verbose ) )
+        {
+        case 0:
+            dvdcss->b_errors = 0;
+            break;
+        case 1:
+            break;
+        case 2:
+            dvdcss->b_debug = 1;
+            break;
+        default:
+            _dvdcss_error( dvdcss, "unknown verbose level, please choose "
+                                   "from '0', '1' or '2'" );
+            free( dvdcss );
+            return NULL;
+            break;
+        }
+    }
 
+    /* Open device */
     i_ret = _dvdcss_open( dvdcss, psz_target );
     if( i_ret < 0 )
     {
@@ -106,21 +158,45 @@ extern dvdcss_handle dvdcss_open ( char *psz_target, int i_flags )
         return NULL;
     }
 
-    i_ret = CSSTest( dvdcss );
-    if( i_ret < 0 )
+#if defined( WIN32 )
+    /* it's not possible to stat a drive letter. Fake a block device */
+    fileinfo.st_mode = S_IFBLK;
+#else
+    if( stat( psz_target, &fileinfo ) < 0 )
     {
-        _dvdcss_error( dvdcss, "css test failed" );
-        _dvdcss_close( dvdcss );
-        free( dvdcss );
-        return NULL;
+        _dvdcss_error( dvdcss, "dvdcss: can't stat target" );
     }
+#endif
 
-    dvdcss->b_encrypted = i_ret;
+    if( S_ISBLK( fileinfo.st_mode ) || 
+        S_ISCHR( fileinfo.st_mode ) )
+    {        
+        i_ret = CSSTest( dvdcss );
+        if( i_ret < 0 )
+        {
+            _dvdcss_error( dvdcss, "CSS test failed" );
+            /* Disable the CSS ioctls and hope that it works? */
+            dvdcss->b_ioctls = 0;
+            dvdcss->b_encrypted = 1;
+        }
+        else
+        {
+            dvdcss->b_ioctls = 1;
+            dvdcss->b_encrypted = i_ret;
+        }
+    }
+    else
+    {
+        _dvdcss_debug( dvdcss, "dvdcss: file mode, using title method" );
+        dvdcss->i_method = DVDCSS_METHOD_TITLE;
+        dvdcss->b_ioctls = 0;
+        dvdcss->b_encrypted = 1;
+    }
 
-    /* If drive is encrypted, crack its key */
-    if( dvdcss->b_encrypted )
+    /* If disc is CSS protected and the ioctls work, authenticate the drive */
+    if( dvdcss->b_encrypted && dvdcss->b_ioctls )
     {
-        i_ret = CSSInit( dvdcss );
+        i_ret = CSSGetDiscKey( dvdcss );
 
         if( i_ret < 0 )
         {
@@ -128,7 +204,17 @@ extern dvdcss_handle dvdcss_open ( char *psz_target, int i_flags )
             free( dvdcss );
             return NULL;
         }
+        
+        memset( dvdcss->css.p_unenc_key, 0, KEY_SIZE );
+    }
+    memset( dvdcss->css.p_title_key, 0, KEY_SIZE );
+
+#ifndef WIN32
+    if( psz_raw_device != NULL )
+    {
+        _dvdcss_raw_open( dvdcss, psz_raw_device );
     }
+#endif
 
     return dvdcss;
 }
@@ -144,19 +230,52 @@ extern char * dvdcss_error ( dvdcss_handle dvdcss )
 /*****************************************************************************
  * dvdcss_seek: seek into the device
  *****************************************************************************/
-extern int dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks )
+extern int dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks, int i_flags )
 {
+    /* title cracking method is too slow to be used at each seek */
+    if( ( ( i_flags & DVDCSS_SEEK_MPEG )
+             && ( dvdcss->i_method != DVDCSS_METHOD_TITLE ) )
+             && dvdcss->b_encrypted )
+    {
+        int     i_ret;
+
+        /* Crack or decrypt CSS title key for current VTS */
+        i_ret = CSSGetTitleKey( dvdcss, i_blocks );
+
+        if( i_ret < 0 )
+        {
+            _dvdcss_error( dvdcss, "fatal error in vts css key" );
+            return i_ret;
+        }
+        else if( i_ret > 0 )
+        {
+            _dvdcss_error( dvdcss, "decryption unavailable" );
+            return -1;
+        }
+    }
+    else if( i_flags & DVDCSS_SEEK_KEY )
+    {
+        /* check the title key */
+        if( dvdcss_title( dvdcss, i_blocks ) ) 
+        {
+            return -1;
+        }
+    }
+
     return _dvdcss_seek( dvdcss, i_blocks );
 }
 
 /*****************************************************************************
- * dvdcss_title: crack the current title key if needed
+ * dvdcss_title: crack or decrypt the current title key if needed
+ *****************************************************************************
+ * This function should only be called by dvdcss_seek and should eventually
+ * not be external if possible.
  *****************************************************************************/
 extern int dvdcss_title ( dvdcss_handle dvdcss, int i_block )
 {
     dvd_title_t *p_title;
-    dvd_key_t p_key;
-    int i_ret;
+    dvd_title_t *p_newtitle;
+    int          i_ret;
 
     if( ! dvdcss->b_encrypted )
     {
@@ -176,11 +295,12 @@ extern int dvdcss_title ( dvdcss_handle dvdcss, int i_block )
          && p_title->i_startlb == i_block )
     {
         /* We've already cracked this key, nothing to do */
+        memcpy( dvdcss->css.p_title_key, p_title->p_key, sizeof(dvd_key_t) );
         return 0;
     }
 
-    /* Crack CSS title key for current VTS */
-    i_ret = CSSGetKey( dvdcss, i_block, p_key );
+    /* Crack or decrypt CSS title key for current VTS */
+    i_ret = CSSGetTitleKey( dvdcss, i_block );
 
     if( i_ret < 0 )
     {
@@ -193,45 +313,39 @@ extern int dvdcss_title ( dvdcss_handle dvdcss, int i_block )
         return -1;
     }
 
-    /* Add key to keytable if it isn't empty */
-    if( p_key[0] | p_key[1] | p_key[2] | p_key[3] | p_key[4] )
+    /* Find our spot in the list */
+    p_newtitle = NULL;
+    p_title = dvdcss->p_titles;
+    while( ( p_title != NULL ) && ( p_title->i_startlb < i_block ) )
     {
-        dvd_title_t *p_newtitle;
-
-        /* Find our spot in the list */
-        p_newtitle = NULL;
-        p_title = dvdcss->p_titles;
-        while( p_title != NULL
-                && p_title->i_startlb < i_block )
-        {
-            p_newtitle = p_title;
-            p_title = p_title->p_next;
-        }
+        p_newtitle = p_title;
+        p_title = p_title->p_next;
+    }
 
-        /* Save the found title */
-        p_title = p_newtitle;
+    /* Save the found title */
+    p_title = p_newtitle;
 
-        /* Write in the new title and its key */
-        p_newtitle = malloc( sizeof( dvd_title_t ) );
-        p_newtitle->i_startlb = i_block;
-        memcpy( p_newtitle->p_key, p_key, KEY_SIZE );
+    /* Write in the new title and its key */
+    p_newtitle = malloc( sizeof( dvd_title_t ) );
+    p_newtitle->i_startlb = i_block;
+    memcpy( p_newtitle->p_key, dvdcss->css.p_title_key, KEY_SIZE );
 
-        /* Link the new title, either at the beginning or inside the list */
-        if( p_title == NULL )
-        {
-            dvdcss->p_titles = p_newtitle;
-            p_newtitle->p_next = NULL;
-        }
-        else
-        {
-            p_newtitle->p_next = p_title->p_next;
-            p_title->p_next = p_newtitle;
-        }
+    /* Link the new title, either at the beginning or inside the list */
+    if( p_title == NULL )
+    {
+        dvdcss->p_titles = p_newtitle;
+        p_newtitle->p_next = NULL;
+    }
+    else
+    {
+        p_newtitle->p_next = p_title->p_next;
+        p_title->p_next = p_newtitle;
     }
 
     return 0;
 }
 
+#define Pkey dvdcss->css.p_title_key
 /*****************************************************************************
  * dvdcss_read: read data from the device, decrypt if requested
  *****************************************************************************/
@@ -239,7 +353,6 @@ extern int dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer,
                                                int i_blocks,
                                                int i_flags )
 {
-    dvd_title_t *p_title;
     int i_ret, i_index;
 
     i_ret = _dvdcss_read( dvdcss, p_buffer, i_blocks );
@@ -251,25 +364,27 @@ extern int dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer,
         return i_ret;
     }
 
-    /* find our key */
-    p_title = dvdcss->p_titles;
-    while( p_title != NULL
-            && p_title->p_next
-            && p_title->p_next->i_startlb < dvdcss->i_seekpos )
+    /* For what we believe is an unencrypted title, 
+       check that there are no encrypted blocks */
+    if( !( Pkey[0] | Pkey[1] | Pkey[2] | Pkey[3] | Pkey[4] ) ) 
     {
-        p_title = p_title->p_next;
-    }
-
-    if( p_title == NULL )
-    {
-        /* no css key found to use, so no decryption to do */
-        return 0;
+        for( i_index = i_ret; i_index; i_index-- )
+        {
+            if( ((u8*)p_buffer)[0x14] & 0x30 )
+            {
+                _dvdcss_error( dvdcss, "no key but found encrypted block" );
+                /* Only return the initial range of unscrambled blocks? */
+                i_ret = i_index;
+                /* or fail completely? return 0; */
+            }
+            (u8*)p_buffer += DVDCSS_BLOCK_SIZE;
+        }
     }
 
     /* Decrypt the blocks we managed to read */
     for( i_index = i_ret; i_index; i_index-- )
     {
-        CSSDescrambleSector( p_title->p_key, p_buffer );
+        CSSDescrambleSector( dvdcss->css.p_title_key, p_buffer );
         ((u8*)p_buffer)[0x14] &= 0x8f;
         (u8*)p_buffer += DVDCSS_BLOCK_SIZE;
     }
@@ -278,14 +393,13 @@ extern int dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer,
 }
 
 /*****************************************************************************
- * dvdcss_readv: read data to an iovec structure, decrypt if reaquested
+ * dvdcss_readv: read data to an iovec structure, decrypt if requested
  *****************************************************************************/
 extern int dvdcss_readv ( dvdcss_handle dvdcss, void *p_iovec,
                                                 int i_blocks,
                                                 int i_flags )
 {
 #define P_IOVEC ((struct iovec*)p_iovec)
-    dvd_title_t *p_title;
     int i_ret, i_index;
     void *iov_base;
     size_t iov_len;
@@ -299,20 +413,6 @@ extern int dvdcss_readv ( dvdcss_handle dvdcss, void *p_iovec,
         return i_ret;
     }
 
-    /* Find our key */
-    p_title = dvdcss->p_titles;
-    while( p_title != NULL
-            && p_title->p_next != NULL
-            && p_title->p_next->i_startlb < dvdcss->i_seekpos )
-    {
-        p_title = p_title->p_next;
-    }
-
-    if( p_title == NULL )
-    {
-        /* no css key found to use, so no decryption to do */
-        return 0;
-    }
 
     /* Initialize loop for decryption */
     iov_base = P_IOVEC->iov_base;
@@ -334,7 +434,7 @@ extern int dvdcss_readv ( dvdcss_handle dvdcss, void *p_iovec,
             iov_len = P_IOVEC->iov_len;
         }
 
-        CSSDescrambleSector( p_title->p_key, iov_base );
+        CSSDescrambleSector( dvdcss->css.p_title_key, iov_base );
         ((u8*)iov_base)[0x14] &= 0x8f;
 
         (u8*)iov_base += DVDCSS_BLOCK_SIZE;
@@ -344,6 +444,7 @@ extern int dvdcss_readv ( dvdcss_handle dvdcss, void *p_iovec,
     return i_ret;
 #undef P_IOVEC
 }
+#undef Pkey
 
 /*****************************************************************************
  * dvdcss_close: close the DVD device and clean up the library
@@ -383,10 +484,25 @@ static int _dvdcss_open ( dvdcss_handle dvdcss, char *psz_target )
     {
         char psz_dvd[7];
         _snprintf( psz_dvd, 7, "\\\\.\\%c:", psz_target[0] );
+
+        /* To have access to ioctls, we need read and write access to the
+         * device. This is only allowed if you have administrator priviledges
+         * so we allow for a fallback method where ioctls are not available but
+         * we at least have read access to the device.
+         * (See Microsoft Q241374: Read and Write Access Required for SCSI
+         * Pass Through Requests) */
         (HANDLE) dvdcss->i_fd =
                 CreateFile( psz_dvd, GENERIC_READ | GENERIC_WRITE,
                                 FILE_SHARE_READ | FILE_SHARE_WRITE,
-                                NULL, OPEN_EXISTING, 0, NULL );
+                                NULL, OPEN_EXISTING,
+                                FILE_FLAG_RANDOM_ACCESS, NULL );
+
+        if( (HANDLE) dvdcss->i_fd == INVALID_HANDLE_VALUE )
+            (HANDLE) dvdcss->i_fd =
+                    CreateFile( psz_dvd, GENERIC_READ, FILE_SHARE_READ,
+                                    NULL, OPEN_EXISTING,
+                                    FILE_FLAG_RANDOM_ACCESS, NULL );
+
         if( (HANDLE) dvdcss->i_fd == INVALID_HANDLE_VALUE )
         {
             _dvdcss_error( dvdcss, "failed opening device" );
@@ -408,7 +524,7 @@ static int _dvdcss_open ( dvdcss_handle dvdcss, char *psz_target )
     dvdcss->i_readv_buf_size = 0;
 
 #else
-    dvdcss->i_fd = open( psz_target, 0 );
+    dvdcss->i_fd = dvdcss->i_read_fd = open( psz_target, 0 );
 
     if( dvdcss->i_fd == -1 )
     {
@@ -421,6 +537,25 @@ static int _dvdcss_open ( dvdcss_handle dvdcss, char *psz_target )
     return 0;
 }
 
+#ifndef WIN32
+static int _dvdcss_raw_open ( dvdcss_handle dvdcss, char *psz_target )
+{
+    dvdcss->i_raw_fd = open( psz_target, 0 );
+
+    if( dvdcss->i_raw_fd == -1 )
+    {
+        _dvdcss_error( dvdcss, "failed opening raw device, continuing" );
+        return -1;
+    }
+    else
+    {
+        dvdcss->i_read_fd = dvdcss->i_raw_fd;
+    }
+
+    return 0;
+}
+#endif
+
 static int _dvdcss_close ( dvdcss_handle dvdcss )
 {
 #if defined( WIN32 )
@@ -444,14 +579,22 @@ static int _dvdcss_close ( dvdcss_handle dvdcss )
 #else
     close( dvdcss->i_fd );
 
+    if( dvdcss->i_raw_fd >= 0 )
+    {
+        close( dvdcss->i_raw_fd );
+        dvdcss->i_raw_fd = -1;
+    }
+
 #endif
 
     return 0;
 }
 
-static int _dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks )
+int _dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks )
 {
 #if defined( WIN32 )
+    dvdcss->i_seekpos = i_blocks;
+
     if( WIN2K )
     {
         LARGE_INTEGER li_read;
@@ -479,11 +622,11 @@ static int _dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks )
         return ( _win32_dvdcss_aseek( dvdcss->i_fd, i_blocks, SEEK_SET ) );
     }
 #else
-    off_t i_read;
+    off_t   i_read;
 
     dvdcss->i_seekpos = i_blocks;
 
-    i_read = lseek( dvdcss->i_fd,
+    i_read = lseek( dvdcss->i_read_fd,
                     (off_t)i_blocks * (off_t)DVDCSS_BLOCK_SIZE, SEEK_SET );
 
     return i_read / DVDCSS_BLOCK_SIZE;
@@ -491,7 +634,7 @@ static int _dvdcss_seek ( dvdcss_handle dvdcss, int i_blocks )
 
 }
 
-static int _dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer, int i_blocks )
+int _dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer, int i_blocks )
 {
 #if defined( WIN32 ) 
     if( WIN2K )
@@ -514,7 +657,7 @@ static int _dvdcss_read ( dvdcss_handle dvdcss, void *p_buffer, int i_blocks )
 #else
     int i_bytes;
 
-    i_bytes = read( dvdcss->i_fd, p_buffer,
+    i_bytes = read( dvdcss->i_read_fd, p_buffer,
                     (size_t)i_blocks * DVDCSS_BLOCK_SIZE );
     return i_bytes / DVDCSS_BLOCK_SIZE;
 #endif
@@ -550,7 +693,7 @@ static int _dvdcss_readv ( dvdcss_handle dvdcss, struct iovec *p_iovec,
     return i_read;
 
 #else
-    i_read = readv( dvdcss->i_fd, p_iovec, i_blocks );
+    i_read = readv( dvdcss->i_read_fd, p_iovec, i_blocks );
     return i_read / DVDCSS_BLOCK_SIZE;
 
 #endif
@@ -802,7 +945,7 @@ static int _win32_dvdcss_aread( int i_fd, void *p_data, int i_blocks )
 
         /* transfer the next 64kb (_win32_dvdcss_aread is called recursively)
          * We need to check the status of the read on return */
-        if( _win32_dvdcss_aread( i_fd, p_data + 32 * DVDCSS_BLOCK_SIZE,
+        if( _win32_dvdcss_aread( i_fd, (u8*) p_data + 32 * DVDCSS_BLOCK_SIZE,
                                  i_blocks - 32) < 0 )
         {
             return -1;