diff --git a/crates/libdvdcss/Cargo.toml b/crates/libdvdcss/Cargo.toml new file mode 100644 index 0000000..3e0fc7e --- /dev/null +++ b/crates/libdvdcss/Cargo.toml @@ -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" diff --git a/crates/libdvdcss/README.md b/crates/libdvdcss/README.md new file mode 100644 index 0000000..6412f3b --- /dev/null +++ b/crates/libdvdcss/README.md @@ -0,0 +1,3 @@ +# libdvdcss Rust Wrapper + +Minimum `libdvdcss` version: 1.6.0 diff --git a/crates/libdvdcss/build.rs b/crates/libdvdcss/build.rs new file mode 100644 index 0000000..c65ba31 --- /dev/null +++ b/crates/libdvdcss/build.rs @@ -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!"); +} diff --git a/crates/libdvdcss/src/dvdcss.rs b/crates/libdvdcss/src/dvdcss.rs new file mode 100644 index 0000000..04d76a4 --- /dev/null +++ b/crates/libdvdcss/src/dvdcss.rs @@ -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")); diff --git a/crates/libdvdcss/src/error.rs b/crates/libdvdcss/src/error.rs new file mode 100644 index 0000000..5d46294 --- /dev/null +++ b/crates/libdvdcss/src/error.rs @@ -0,0 +1,17 @@ +use derive_more::{Display, From}; + +pub type Result = std::result::Result; + +#[derive(Debug, From, Display)] +pub enum Error { + #[display("I32ToEnumCastError {{ {}, {} }}", value, enum_)] + I32ToEnumCastError { + value: i32, + enum_: String, + }, + InsufficientBufferLength, + ReadError { + value: i32, + }, + SeekError, +} diff --git a/crates/libdvdcss/src/lib.rs b/crates/libdvdcss/src/lib.rs new file mode 100644 index 0000000..76dca2a --- /dev/null +++ b/crates/libdvdcss/src/lib.rs @@ -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 for EncryptionScheme { + type Error = Error; + + fn try_from(value: i32) -> std::result::Result { + 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>(src: P) -> Option { + // 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 { + // 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 { + // 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); + } +} diff --git a/crates/libdvdcss/src/wrapper.h b/crates/libdvdcss/src/wrapper.h new file mode 100644 index 0000000..e5d92ea --- /dev/null +++ b/crates/libdvdcss/src/wrapper.h @@ -0,0 +1 @@ +#include