[libdvdcss-devel] [Git][videolan/libdvdcss][master] 11 commits: CPRM: adjust cpxm struct member naming

Jean-Baptiste Kempf (@jbk) gitlab at videolan.org
Sun Jan 18 23:02:46 UTC 2026



Jean-Baptiste Kempf pushed to branch master at VideoLAN / libdvdcss


Commits:
e72928cd by Saifelden Mohamed Ismail at 2026-01-16T23:16:53+02:00
CPRM: adjust cpxm struct member naming

- - - - -
290f9b5b by Saifelden Mohamed Ismail at 2026-01-16T23:16:53+02:00
CPRM: add apstb to cpxm struct

- - - - -
7db494df by Saifelden Mohamed Ismail at 2026-01-16T23:16:53+02:00
CPRM: remove unused vr_get_k_t

- - - - -
b5d13027 by Saifelden Mohamed Ismail at 2026-01-16T23:16:53+02:00
CPXM: use calloc for cpxm struct

- - - - -
871ecbfb by Saifelden Mohamed Ismail at 2026-01-16T23:35:24+02:00
CPRM: add initialization

- - - - -
81297e5c by Saifelden Mohamed Ismail at 2026-01-17T13:06:04+02:00
CPRM: fix decryption function

- - - - -
9bcf7a12 by Saifelden Mohamed Ismail at 2026-01-17T13:06:06+02:00
nitpick: remove unused psz_file[PATH_MAX]

- - - - -
198e61b6 by Saifelden Mohamed Ismail at 2026-01-17T13:06:06+02:00
CPRM: update header doc for dvdcpxm_init

- - - - -
f6191520 by Saifelden Mohamed Ismail at 2026-01-17T13:06:06+02:00
CPRM: fix dvdcpxm_seek unused variable warning

- - - - -
103b58a7 by Saifelden Mohamed Ismail at 2026-01-17T13:06:06+02:00
API: add dvdcss_get_encryption_type

build: update soname and version

- - - - -
2682a4a7 by Saifelden Mohamed Ismail at 2026-01-17T13:10:41+02:00
update NEWS to 1.6.0

- - - - -


7 changed files:

- NEWS
- meson.build
- src/cpxm.h
- src/dvdcss/dvdcpxm.h
- src/dvdcss/dvdcss.h
- src/libdvdcpxm.c
- src/libdvdcss.c


Changes:

=====================================
NEWS
=====================================
@@ -1,3 +1,15 @@
+--------------------------------
+Changes between 1.5.0 and 1.6.0:
+--------------------------------
+
+  * Add dvdcss_get_encryption_type function to the public API
+    This allows applications to query the disc's copyright protection system
+    type (CPST) directly from the drive, enabling better distinction between
+    CSS/CPPM and CPRM protected media during initialization.
+  * Added support for CPRM (Content Protection for Recordable Media) decryption.
+  * Updated dvdcpxm_setup to allow context reuse via NULL arguments and to accept a raw CPRM key in place of the MKB.
+
+
 Changes between 1.4.3 and 1.5.0:
 --------------------------------
 


=====================================
meson.build
=====================================
@@ -1,5 +1,5 @@
 project('libdvdcss', 'c',
-    version: '1.5.0',
+    version: '1.6.0',
     meson_version: '>= 0.60.0',
     default_options: ['buildtype=debugoptimized',
                       'c_std=c17',
@@ -17,7 +17,7 @@ cdata = configuration_data()
 dvdcss_inc_dirs = include_directories('.', 'src', 'src/dvdcss')
 
 # The version number for the shared library
-dvdcss_soname_version = '2.3.0'
+dvdcss_soname_version = '2.4.0'
 
 dvdcss_version = meson.project_version()
 dvdcss_version_split = dvdcss_version.split('.')


=====================================
src/cpxm.h
=====================================
@@ -50,7 +50,8 @@ typedef struct cpxm
    uint64_t media_key;
    uint64_t id_album;
    uint64_t id_media;
-   uint64_t vr_k_te;
+   uint64_t vr_k_t;
+   uint64_t apstb;
 } cpxm_s;
 
 /* for persistance */


=====================================
src/dvdcss/dvdcpxm.h
=====================================
@@ -67,7 +67,9 @@ extern "C" {
  */
 
 /* Called after the DVD is opened to initialize the cpxm struct, Must be run after dvdcss_open */
-LIBDVDCSS_EXPORT int dvdcpxm_init(dvdcss_t dvdcss, uint8_t *p_mkb);
+/* in the case of DVD-Audio, the MKB must be given as input, 
+ * in the case of DVD-VR, the title key should be given as input */
+LIBDVDCSS_EXPORT int dvdcpxm_init(dvdcss_t dvdcss, uint8_t *p_input);
 
 /* Same as dvdcss_close but frees the cpxm struct as well */
 LIBDVDCSS_EXPORT int dvdcpxm_close ( dvdcss_t dvdcss );


=====================================
src/dvdcss/dvdcss.h
=====================================
@@ -106,6 +106,8 @@ LIBDVDCSS_EXPORT const char *dvdcss_error ( const dvdcss_t );
 
 LIBDVDCSS_EXPORT int      dvdcss_is_scrambled ( dvdcss_t );
 
+LIBDVDCSS_EXPORT int dvdcss_get_encryption_type ( dvdcss_t );
+
 #ifdef __cplusplus
 }
 #endif


=====================================
src/libdvdcpxm.c
=====================================
@@ -495,43 +495,14 @@ int process_mkb( uint8_t *p_mkb, device_key_t *p_dev_keys, int nr_dev_keys, uint
     return -1;
 }
 
-int vr_get_k_te( p_cpxm cpxm,char *psz_vr_mangr )
-{
-    FILE    *f_vr_m;
-    uint8_t vmgi_mat[512];
-    int ret = -1;
-
-    f_vr_m = fopen( psz_vr_mangr, "rb" );
-
-    if ( !f_vr_m )
-        return -1;
-
-    if ( fread(vmgi_mat, 1, 512, f_vr_m) == 512 )
-    {
-        if ( memcmp( vmgi_mat, "DVD_RTR_VMG0", 12 ) == 0 )
-        {
-            cpxm->vr_k_te = 0;
-            if ( vmgi_mat[267] & 1 ) /* Check encrypted title key status bit */
-            {
-                memcpy( &cpxm->vr_k_te, &vmgi_mat[268], sizeof( cpxm->vr_k_te ) );
-                B2N_64( cpxm->vr_k_te );
-            }
-            ret = 0;
-        }
-    }
-
-    fclose( f_vr_m );
-    return ret;
-}
-
 /* Function should be called on a dvdcss var to set cppm struct which needs
  * to persist in order to decrypt the media */
-LIBDVDCSS_EXPORT int dvdcpxm_init( dvdcss_t dvdcss, uint8_t *p_mkb )
+LIBDVDCSS_EXPORT int dvdcpxm_init( dvdcss_t dvdcss, uint8_t *p_input )
 {
     /* In the case that p_mkb is received as null, then either you were unable
      * to read the mkb or the encryption type is cprm */
     /* if no file mkb file is passed, check cache */
-    if (!p_mkb)
+    if (!p_input)
     {
         cpxm_cache *cpxm_iterator = g_cpxm_cache;
         struct stat file_stat;
@@ -550,15 +521,16 @@ LIBDVDCSS_EXPORT int dvdcpxm_init( dvdcss_t dvdcss, uint8_t *p_mkb )
         return -1;
     }
 
-    p_cpxm cpxm = malloc(sizeof(cpxm_s));
+    p_cpxm cpxm = calloc(1, sizeof(cpxm_s));
     if (!cpxm)
        return -1;
 
     dvdcss->cpxm = cpxm;
 
-    char psz_file[PATH_MAX];
     int ret = -1;
 
+    uint8_t *p_mkb;
+
     c2_init();
     switch ( dvdcss->media_type )
     {
@@ -566,6 +538,8 @@ LIBDVDCSS_EXPORT int dvdcpxm_init( dvdcss_t dvdcss, uint8_t *p_mkb )
             ret = 0;
             break;
         case COPYRIGHT_PROTECTION_CPPM:
+            /* the input is the media key block */
+            p_mkb = p_input;
             if ( cppm_set_id_album( dvdcss ) == 0 )
             {
                 if ( p_mkb )
@@ -590,15 +564,19 @@ LIBDVDCSS_EXPORT int dvdcpxm_init( dvdcss_t dvdcss, uint8_t *p_mkb )
                     free( p_mkb );
                     if (ret) break;
                 }
-                snprintf( psz_file, PATH_MAX, "%s/DVD_RTAV/VR_MANGR.IFO", dvdcss->psz_device );
-                ret = vr_get_k_te( dvdcss->cpxm,psz_file );
 
-                if (ret)
-                {
-                    snprintf( psz_file, PATH_MAX, "%s/DVD_RTAV/VR_MANGR.BUP", dvdcss->psz_device );
-                    ret = vr_get_k_te( dvdcss->cpxm,psz_file );
-                    if (ret) break;
-                }
+
+                /* get the media unique key */
+                uint64_t k_mu = c2_g( cpxm->media_key, cpxm->id_media ) & 0x00ffffffffffffff;
+
+                /* decrypt the encrypted title key */
+                uint64_t k_te;
+                READ64_BE( k_te , p_input );
+                uint64_t k_t = c2_dec( k_mu, k_te ) & 0x00ffffffffffffff;
+
+                /* store decrypted title key for vr decryption */
+                cpxm->vr_k_t = k_t;
+
             }
             break;
     }
@@ -731,34 +709,48 @@ int cppm_decrypt_block( uint8_t *p_buffer, int flags, uint64_t id_album, uint64_
     return encrypted;
 }
 
-int cprm_decrypt_block( uint8_t *p_buffer, int flags, uint64_t id_album, uint64_t media_key )
+/*
+ * check if MPEG Video headers exist
+ * Returns:
+ * 1 = Decryption successful
+ * 0 = Decryption failed
+ */
+int is_valid_mpeg_payload(uint8_t *buffer) {
+    for (size_t i = 0; i < DVDCPXM_BLOCK_SIZE - 4; i++) {
+        /* Look for the Start Code Prefix (00 00 01) */
+        if (buffer[i] == 0x00 && buffer[i+1] == 0x00 && buffer[i+2] == 0x01) {
+            uint8_t code = buffer[i+3];
+            if (code == 0xB3) return 1; // Sequence Header
+            if (code == 0xB8) return 1; // GOP Header
+            if (code == 0x00) return 1; // Picture Header
+        }
+    }
+    return 0;
+}
+
+int cprm_decrypt_block( uint8_t *p_buffer, int flags, uint64_t vr_k_t, uint64_t apstb )
 {
-    uint64_t d_kc_i, k_au, k_i, k_c;
+    uint64_t d_tkc, k_i, k_c;
     int encrypted;
 
     encrypted = 0;
     if ( mpeg2_check_pes_scrambling_control( p_buffer ) )
     {
-        k_au = c2_g( id_album, media_key ) & 0x00ffffffffffffff;
-
-        READ64_BE( d_kc_i, &p_buffer[24] );
-        k_i = c2_g( d_kc_i, k_au ) & 0x00ffffffffffffff;
-
-        READ64_BE( d_kc_i, &p_buffer[32] );
-        k_i = c2_g( d_kc_i, k_i ) & 0x00ffffffffffffff;
+        /* Add the CPRM_CI byte (or APSTB bits) to the title key to get K_i.
+         * We treat this as a full byte (0-255) to support both DVD-VR and DVD-Video. */
+        k_i = vr_k_t + ( apstb );
 
-        READ64_BE( d_kc_i, &p_buffer[40] );
-        k_i = c2_g( d_kc_i, k_i ) & 0x00ffffffffffffff;
-
-        READ64_BE( d_kc_i, &p_buffer[48] );
-        k_i = c2_g( d_kc_i, k_i ) & 0x00ffffffffffffff;
-
-        READ64_BE( d_kc_i, &p_buffer[84] );
-        k_c = c2_g( d_kc_i, k_i ) & 0x00ffffffffffffff;
+        /* read the Title Key Conversion Data */
+        READ64_BE( d_tkc , &p_buffer[84] );
+        k_c = c2_g( k_i, d_tkc )  & 0x00ffffffffffffff;
 
         c2_dcbc( &p_buffer[DVDCPXM_BLOCK_SIZE - DVDCPXM_ENCRYPTED_SIZE], k_c, DVDCPXM_ENCRYPTED_SIZE );
         mpeg2_reset_pes_scrambling_control( p_buffer );
-        encrypted = 1;
+        /* check if decryption failed */
+        if ( is_valid_mpeg_payload( p_buffer ) )
+            encrypted = 1;
+        else
+            encrypted = -1;
     }
 
     if ( ( flags & DVDCPXM_PRESERVE_CCI ) != DVDCPXM_PRESERVE_CCI )
@@ -774,7 +766,44 @@ int dvdcpxm_decrypt( p_cpxm cpxm, int media_type,void *p_buffer, int flags )
         case COPYRIGHT_PROTECTION_CPPM:
             return cppm_decrypt_block( (uint8_t *) p_buffer, flags, cpxm->id_album, cpxm->media_key );
         case COPYRIGHT_PROTECTION_CPRM:
-            return cprm_decrypt_block( (uint8_t* ) p_buffer, flags, cpxm->id_album ,cpxm->media_key );
+        {
+            /* return early if there is no encryption to avoid allocating 2kb */
+            if ( !mpeg2_check_pes_scrambling_control( p_buffer ) )
+                return cprm_decrypt_block( (uint8_t* ) p_buffer, flags, cpxm->vr_k_t, cpxm->apstb );
+
+            /* we are not sure if apstb is correct, so we operate on a copied buffer first */
+            uint8_t temp_buffer[DVDCPXM_BLOCK_SIZE];
+
+            /* For DVD-VR we only need to check the first 4 possible values since apstb is 2 bits
+             * for DVD-Video with CPRM we need to check more, since CPRM_CI is more bits */
+            uint64_t nr_possible_values = 256;
+
+            /* assume the retained value is initially correct */
+            memcpy(temp_buffer, p_buffer, DVDCPXM_BLOCK_SIZE);
+            int result = cprm_decrypt_block( temp_buffer, flags, cpxm->vr_k_t, cpxm->apstb );
+
+            if ( result == 1 ) {
+                memcpy(p_buffer, temp_buffer, DVDCPXM_BLOCK_SIZE);
+                return result;
+            }
+
+            /* our old value must not have been correct, we must guess */
+            for (  uint64_t guess = 0; guess < nr_possible_values ; guess++ ) {
+
+                /* skip if we already checked this above */
+                if ( guess == cpxm->apstb ) continue;
+
+                /* try our value */
+                memcpy(temp_buffer, p_buffer, DVDCPXM_BLOCK_SIZE);
+                result = cprm_decrypt_block( temp_buffer, flags, cpxm->vr_k_t, guess );
+                if ( result == 1 ) {
+                     memcpy(p_buffer, temp_buffer, DVDCPXM_BLOCK_SIZE);
+                     cpxm->apstb = guess;
+                     return result;
+                }
+            }
+
+        }
     }
 
     return 0;
@@ -855,6 +884,7 @@ int dvdcpxm_read ( dvdcss_t dvdcss, void *p_buffer,
 
 int dvdcpxm_seek ( dvdcss_t dvdcss, int i_blocks, int i_flags )
 {
+    (void)i_flags;
     return dvdcss_seek( dvdcss, i_blocks, DVDCSS_NOFLAGS );
 }
 


=====================================
src/libdvdcss.c
=====================================
@@ -872,3 +872,13 @@ LIBDVDCSS_EXPORT int dvdcss_is_scrambled ( dvdcss_t dvdcss )
 {
     return dvdcss->b_scrambled;
 }
+
+/**
+ * \brief Return the type of encryption
+ *
+ * \return 0 if the disc is unencrypted, 1 if cppm or css, 2 if cprm
+ */
+LIBDVDCSS_EXPORT int dvdcss_get_encryption_type ( dvdcss_t dvdcss )
+{
+    return dvdcss->media_type;
+}



View it on GitLab: https://code.videolan.org/videolan/libdvdcss/-/compare/a69b73ea32857043ba9911e3092906c18676d031...2682a4a7ed782e700a5b920f6f85c4f9736921c3

-- 
View it on GitLab: https://code.videolan.org/videolan/libdvdcss/-/compare/a69b73ea32857043ba9911e3092906c18676d031...2682a4a7ed782e700a5b920f6f85c4f9736921c3
You're receiving this email because of your account on code.videolan.org.


VideoLAN code repository instance


More information about the libdvdcss-devel mailing list