When the drive is in extended-access state (unlocked), retrieve VID via the per-drive `read_vid_cdb` from the bundled profile instead of the cert-based AACS REPORT_KEY handshake. Cert handshake remains the fallback for drives that don't enter extended-access state, or whose profile lacks the required CDB. Empirically verified on the BU40N (signature 999ec375) against Barbie UHD: drive returns 36 bytes from buffer 0x44 at offset 0x10E291, VID at response[4..20]. The 16 bytes match Dune Part Two's known VID in keydb.cfg byte-for-byte, cross-validating the path against an independent oracle. Architectural impact: - Renames `Drive::is_libredrive_active()` → `Drive::is_unlocked()`. Internal `Mt1959::libredrive_active` becomes `Mt1959::unlocked`; the prior `unlocked` (init-success flag) becomes `init_complete` to avoid the name collision. - `disc/encrypt.rs::Disc::read_vid` is the single entry point. When `is_unlocked()` is true, calls `read_vid_oem` (issues the per-drive CDB, validates the response signature high-3-bytes `00 22 00`, returns bytes [4..20]). Otherwise delegates to `read_vid_cert` (the existing AACS REPORT_KEY format 0x80 path). - `DriveProfile` gains the per-drive CDB templates and identifier blocks extracted from each per-drive firmware payload — including `read_vid_cdb`, `read_disc_keys_cdb`, `drive_nominal_speed_cdb`, `set_speed_max_cdb`, two cache-prime canary CDBs, the buffer-0x45 verify CDB, the firmware-upload CDB, and the unlock probe CDB. Variants A and B differ in which fields are populated. All optional; consumers fall back to the cert/handshake path when fields are absent. - New error variants `Error::DriveProfileMissing` (E7020) and `Error::VidCdbUnavailable` (E7021). Both treated as "OEM unavailable → try cert path" by `read_vid`, not terminal. Closes the v0.25.x gap where HRL-burned host certs (the public libaacs leaked cert is on every recent drive's HRL) blocked all post-handshake VID retrieval. With OEM-driven VID: - AACS 1.0 BD on supported drives: rips end-to-end with our existing DKs walking the MKB. - AACS 2.x UHD: fails honestly at the DK wall (E7018 "No usable DK" for v77+ MKBs) instead of the misleading E7017 "No Volume ID" the prior code surfaced. We have VID; we just don't have v77+ DK material — that gap is a key-acquisition problem, not a code problem. Empirically verified on rip1 (BU40N + Barbie UHD, MKB v77, 2026-05-21): error code flipped from E7017 to E7018 as predicted. The DK wall is now correctly the proximate failure for unrippable modern UHD discs, instead of the indirect VID-retrieval wall the v0.25.x cert-only path produced. Renames and comment scrubs eliminate upstream-RE-vocabulary references in the public crate per `feedback_no_breadcrumbs.md`. 674 tests pass (565 lib + 109 integration). No tradename leaks in any modified file.
libfreemkv
Rust library for 4K UHD / Blu-ray / DVD optical drives. Drive access, disc scanning, stream labels, AACS decryption, CSS decryption, KEYDB updates, and content reading in one crate. Bundled drive profiles — no external files needed.
Built-in keys cover DVDs and Blu-rays (AACS 1.0). For UHD (AACS 2.0 / 2.1) discs, an optional keydb.cfg supplies disc-specific volume unique keys.
12+ MB/s sustained read speeds on BD. Full init: unlock, firmware upload, speed calibration — all from pure Rust.
Multi-lingual by design — the library outputs structured data and numeric error codes, never English text. Build any UI or localization on top.
API Documentation · Technical Docs
Part of the freemkv project.
Install
[dependencies]
libfreemkv = "0.25"
Quick Start
use libfreemkv::{Drive, Disc, ScanOptions};
use std::path::Path;
// Open drive — profiles are bundled, auto-identified
let mut drive = Drive::open(Path::new("/dev/sg4"))?;
drive.wait_ready()?; // wait for disc
drive.init()?; // unlock + firmware upload
drive.probe_disc()?; // probe disc surface for optimal speeds
// Scan disc — UDF, playlists, streams, AACS (all automatic)
let disc = Disc::scan(&mut drive, &ScanOptions::default())?;
for title in &disc.titles {
println!("{} — {} streams", title.duration_display(), title.streams.len());
}
// Stream pipeline — read PES frames from any source, write to any output
let opts = libfreemkv::InputOptions::default();
let mut input = libfreemkv::input("iso://Disc.iso", &opts)?;
let title = input.info().clone();
let mut output = libfreemkv::output("mkv://Movie.mkv", &title)?;
while let Ok(Some(frame)) = input.read() {
output.write(&frame)?;
}
output.finish()?;
Multi-pass recovery rip
For damaged discs the library exposes two flat verbs — Disc::sweep for the
forward Pass 1 and Disc::patch for retrying bad ranges. The library never
loops; the multipass policy is the caller's job. See
docs/rip-recovery.md.
use libfreemkv::{SweepOptions, PatchOptions};
use libfreemkv::disc::{mapfile, mapfile_path_for};
use std::path::Path;
let iso = Path::new("disc.iso");
// Pass 1: disc → ISO. Skip-on-error, zero-fill, write the sidecar mapfile.
disc.sweep(&mut drive, iso, &SweepOptions {
decrypt: true,
resume: false,
batch_sectors: None,
skip_on_error: true,
progress: None,
halt: None,
})?;
// Pass 2..N: retry every non-finished range. Idempotent.
loop {
let map = mapfile::Mapfile::load(&mapfile_path_for(iso))?;
let stats = map.stats();
if stats.bytes_pending + stats.bytes_unreadable == 0 { break; }
let outcome = disc.patch(&mut drive, iso, &PatchOptions {
decrypt: true,
block_sectors: None,
full_recovery: true,
reverse: true,
wedged_threshold: 50,
progress: None,
halt: None,
})?;
if outcome.bytes_recovered_this_pass == 0 { break; }
}
// Mux from the ISO via the normal stream pipeline (no drive involvement).
What It Does
- Drive access — open, identify, unlock, firmware upload, speed calibration, eject
- 12+ MB/s reads — auto-detects kernel transfer limits, sustained full speed
- Disc scanning — UDF 2.50 filesystem, MPLS playlists, CLPI clip info
- Stream labels — 5 BD-J format parsers (Paramount, Criterion, Pixelogic, CTRM, Deluxe)
- AACS decryption — transparent key resolution and content decrypt (1.0 + 2.0 bus decryption)
- KEYDB updates — download, verify, save from any HTTP URL (zero deps, raw TCP)
- Content reading — adaptive batch reads with automatic decryption
- Stream I/O — unified stream pipeline for reading and writing any format
Streams
| Stream | Input | Output | Transport |
|---|---|---|---|
| DiscStream | Yes | -- | Optical drive via SCSI |
| IsoStream | Yes | -- | Blu-ray ISO image file (read via stream pipeline; written via Disc::sweep()) |
| MkvStream | Yes | Yes | Matroska container |
| M2tsStream | Yes | Yes | BD transport stream with FMKV metadata header |
| NetworkStream | Yes (listen) | Yes (connect) | TCP with FMKV metadata header |
| StdioStream | Yes (stdin) | Yes (stdout) | Raw byte pipe |
| NullStream | -- | Yes | Discard sink (byte counter for benchmarks) |
Streams implement FrameSource (read) and/or FrameSink (write); direction is type-checked. input() / output() resolve URL strings to PES stream instances. All URLs use the scheme://path format — bare paths are rejected.
Keys
DVDs (CSS) decrypt out of the box — the 1999-era public player keys are compiled into the library.
Blu-rays and UHD (AACS) require a keydb.cfg at ~/.config/freemkv/keydb.cfg (or passed via ScanOptions). The file holds all DKs, PKs, host certs, and per-disc VUKs. No AACS key material is compiled into the binary.
Architecture
Drive — open, identify, init, unlock, single-shot read
├── ScsiTransport — SG_IO (Linux), IOKit (macOS), SPTI (Windows)
├── DriveProfile — per-drive unlock parameters (bundled)
└── PlatformDriver — MediaTek (supported), Renesas (planned)
Disc — scan titles, streams, AACS/CSS state
├── UDF reader — Blu-ray UDF 2.50 with metadata partitions
├── MPLS parser — playlists → titles + clips + streams
├── CLPI parser — clip info → EP map → sector extents
├── IFO parser — DVD title sets, PGC chains, cell addresses
├── Labels — 5 BD-J format parsers (detect + parse)
├── AACS — key resolution + content decryption
├── CSS — DVD CSS cipher (table-driven, no keys needed)
└── KEYDB — download + verify + save
Streams — unified PES pipeline
├── FrameSource — read() PES frames (direction-typed)
├── FrameSink — write() PES frames (direction-typed)
├── DiscStream — sectors → decrypt → TS demux → PES
├── IsoStream — ISO file → decrypt → TS demux → PES
├── MkvStream — MKV mux/demux
├── M2tsStream — BD transport stream
├── NetworkStream — TCP with FMKV metadata header
├── StdioStream — stdin/stdout pipe
└── NullStream — discard sink
See docs/ for detailed technical documentation on each module.
Error Codes
All errors are structured with numeric codes. No user-facing English text — applications format their own messages.
| Range | Category |
|---|---|
| E1xxx | Device errors (not found, permission) |
| E2xxx | Profile errors (unsupported drive) |
| E3xxx | Unlock errors (failed, signature) |
| E4xxx | SCSI errors (command failed, timeout) |
| E5xxx | I/O errors |
| E6xxx | Disc format errors |
| E7xxx | AACS errors |
| E8xxx | KEYDB update errors |
Platform Support
| Platform | Status | Backend |
|---|---|---|
| Linux | Supported | SG_IO ioctl |
| macOS | Supported | IOKit SCSITask |
| Windows | Supported | SPTI |
Contributing
Run freemkv info disc:// --share with the freemkv CLI to contribute your drive's profile.
License
AGPL-3.0-only