Introduce libdvdcss wrapper.

- This is here for devices that may falsely request bus encryption due
  to bit flips in MPEG-2 PACK headers.
- Not yet made an optional feature; need to figure out how to even talk
  with a drive over SCSI.
This commit is contained in:
2026-06-21 10:58:38 -04:00
parent 1264340154
commit 592bffbd3a
7 changed files with 228 additions and 0 deletions
+15
View File
@@ -0,0 +1,15 @@
[package]
name = "libdvdcss"
version = "0.1.0"
edition.workspace = true
authors.workspace = true
repository.workspace = true
license.workspace = true
publish.workspace = true
[dependencies]
derive_more.workspace = true
semver.workspace = true
[build-dependencies]
bindgen = "0.72.1"
+3
View File
@@ -0,0 +1,3 @@
# libdvdcss Rust Wrapper
Minimum `libdvdcss` version: 1.6.0
+24
View File
@@ -0,0 +1,24 @@
use std::env;
use std::path::PathBuf;
fn main() {
// Path from which to search for shared libraries.
// Is there no better (automated), platform-specific way?
println!("cargo:rustc-link-search=/usr/lib");
// Locate and link the shared library.
println!("cargo:rustc-link-lib=libdvdcss");
let bindings = bindgen::Builder::default()
.header("src/wrapper.h")
// Invalidate the build if a header has changed.
.parse_callbacks(Box::new(bindgen::CargoCallbacks::new()))
.generate()
.expect("Failed to generate bindings.");
// Write the bindings to the $OUT_DIR/bindings.rs file.
let out_path = PathBuf::from(env::var("OUT_DIR").unwrap());
bindings
.write_to_file(out_path.join("bindings.rs"))
.expect("Couldn't write bindings!");
}
+6
View File
@@ -0,0 +1,6 @@
#![allow(non_upper_case_globals)]
#![allow(non_camel_case_types)]
#![allow(non_snake_case)]
#![allow(unused)]
include!(concat!(env!("OUT_DIR"), "/bindings.rs"));
+17
View File
@@ -0,0 +1,17 @@
use derive_more::{Display, From};
pub type Result<T> = std::result::Result<T, Error>;
#[derive(Debug, From, Display)]
pub enum Error {
#[display("I32ToEnumCastError {{ {}, {} }}", value, enum_)]
I32ToEnumCastError {
value: i32,
enum_: String,
},
InsufficientBufferLength,
ReadError {
value: i32,
},
SeekError,
}
+162
View File
@@ -0,0 +1,162 @@
use std::ffi::{CStr, c_int, c_void};
use std::path::Path;
use crate::error::{Error, Result};
mod dvdcss;
mod error;
use dvdcss::*;
use semver::Version;
pub const BLOCK_SIZE: u32 = DVDCSS_BLOCK_SIZE;
pub const DVDCSS_VERSION: semver::Version = Version::new(
DVDCSS_VERSION_MAJOR as u64,
DVDCSS_VERSION_MINOR as u64,
DVDCSS_VERSION_MICRO as u64,
);
#[repr(u32)]
#[derive(Debug, Default)]
pub enum SeekFlag {
#[default]
None = DVDCSS_NOFLAGS,
Key = DVDCSS_SEEK_KEY,
Mpeg = DVDCSS_SEEK_MPEG,
}
// I dislike this as technically, 2 should be Unknown/Reserved and 3 as CPRM.
#[repr(i32)]
#[derive(Debug, Default)]
pub enum EncryptionScheme {
#[default]
None = 0,
CssCppm = 1,
//Reserved,
Cprm = 2,
Unknown,
}
impl TryFrom<i32> for EncryptionScheme {
type Error = Error;
fn try_from(value: i32) -> std::result::Result<Self, Self::Error> {
if !(0..=2).contains(&value) {
return Err(Error::I32ToEnumCastError {
value,
enum_: "EncryptionScheme".to_string(),
});
}
Ok(match value {
0 => Self::None,
1 => Self::CssCppm,
2 => Self::Cprm,
_ => unreachable!(),
})
}
}
#[repr(transparent)]
pub struct DVD {
ptr: dvdcss_t,
}
impl DVD {
pub fn new<P: AsRef<Path>>(src: P) -> Option<Self> {
// Cast the path to a CStr (*const char), returning early if there's any issue.
let c_path = CStr::from_bytes_with_nul(src.as_ref().as_os_str().as_encoded_bytes())
.ok()?
.as_ptr();
// Make the FFI call.
let ptr = unsafe { dvdcss_open(c_path) };
if ptr.is_null() {
None
} else {
Some(Self { ptr })
}
}
// Seek in blocks (See: `BLOCK_SIZE`).
pub fn seek(&self, blocks: u32, flag: SeekFlag) -> Result<u32> {
// Although this call generally returns a negative value on fail, as of writing
// it can only return -1, so there's no point in expanding this fail result into
// a proper enum.
let retval = unsafe { dvdcss_seek(self.ptr, blocks as c_int, flag as c_int) };
if retval < 0 {
Err(Error::SeekError)
} else {
Ok(retval as u32)
}
}
pub fn read(&self, buf: &mut [u8], blocks: u32, decrypt: bool) -> Result<u32> {
// If the user has provided a wonk buffer size that's not a multiple of BLOCK_SIZE,
// I'm not fixing it for them.
// But ensure that buffer is big enough to safely read the data.
if buf.len() < (blocks * BLOCK_SIZE) as usize {
return Err(Error::InsufficientBufferLength);
}
let i_flags = if decrypt {
DVDCSS_READ_DECRYPT
} else {
DVDCSS_NOFLAGS
} as i32;
let retval = unsafe {
dvdcss_read(
self.ptr,
buf.as_mut_ptr() as *mut c_void,
blocks as c_int,
i_flags,
)
};
if retval < 0 {
// I can't track back the error return values for pf_read,
// so this is good enough for now.
return Err(Error::ReadError { value: retval });
}
Ok(retval as u32)
}
// Return the error message string from dvdcss_error
pub fn error(&self) -> &CStr {
unsafe { CStr::from_ptr(dvdcss_error(self.ptr)) }
}
// Return the error message string from dvdcss_error with lossy conversion.
pub fn error_lossy(&self) -> String {
self.error().to_string_lossy().to_string()
}
pub fn is_scrambled(&self) -> bool {
match unsafe { dvdcss_is_scrambled(self.ptr) } {
0 => false,
1 => true,
_ => unreachable!(),
}
}
/*
pub fn encryption_type(&self) -> EncryptionScheme {
unsafe { dvdcss_get_encryption_type(self.ptr) }
.try_into()
.unwrap_or_default()
}
*/
}
impl Drop for DVD {
fn drop(&mut self) {
// Right... so drop can't handle return values.
// So... best option is ...panic?
let retval = unsafe { dvdcss_close(self.ptr) };
assert!(retval == 0);
}
}
+1
View File
@@ -0,0 +1 @@
#include <dvdcss/dvdcss.h>