Files
kramer/crates/libdvdcss/src/lib.rs
T
Cutieguwu 592bffbd3a 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.
2026-06-21 10:58:38 -04:00

163 lines
4.2 KiB
Rust

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);
}
}