The HD DVD Title Key File (VTKF*.AACS) stores 64 title-key entries of 36 bytes each — 1-byte BIFO + 3 reserved + 16-byte encrypted key + 16-byte binding MAC — per AACS "HD DVD and DVD Pre-recorded Book" Table 3-8, confirmed byte-exact against real discs (Freedom VTKF090, Dukes VTKF000: every 36-byte slot has BIFO=0x80, a clean key, and a 0xFF binding MAC). The parser used a 32-byte stride (a 12-byte pad in place of the 16-byte binding MAC) with flag-based termination. That aligns entry #1 (key at offset 132, where both strides agree) but drifts +4 bytes per entry after it and never terminates (the previous entry's 0xFF MAC reads as a set present-flag), so it recovered a correct key only for single-CPS-unit discs and garbage for CPS unit >=2. Every multi-title HD DVD (Freedom, Harry Potter) was affected. Fix: 36-byte stride, iterate the fixed 64 slots, take slots whose BIFO AV_FLG (bit 7) is set, key at offset 4, slot index = CPS unit (skip empty slots rather than terminate so a gap can't renumber later keys), and never read the trailing 16-byte TKF MAC as a key. Tests rebuilt on the real layout, including a full 64-entry file. Also correct the VTKF-selection TODO in mod.rs: the AACS HD DVD Book gives the selector explicitly (match the TKF's PLAYLIST_NAME field to the active playlist), not the "validate against an encrypted unit" placeholder. Reconciled against the new HD DVD reference (freemkv.org/docs/hddvd/); the spec source is archived in freemkv-private/spec/.
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. Drive-level unlocking is handled internally; consumers work with disc access and decryption only.
DVDs (CSS) decrypt out of the box. Blu-ray and UHD (AACS) require a keydb.cfg (default ~/.config/freemkv/keydb.cfg) supplying disc-specific volume unique keys; no AACS key material is compiled in.
12+ MB/s sustained read speeds on BD. Drive prep (init()) handles unlocking internally via the freemkv-unlock crate — clients never see it; when no drive unlock applies, the library rips via the host-certificate AACS handshake.
Multi-lingual by design — the library outputs structured data and numeric error codes, never English text. Build any UI or localization on top.
Part of the freemkv project.
Install
Consumed by git tag (not published to crates.io):
[dependencies]
libfreemkv = { git = "https://github.com/freemkv/libfreemkv", tag = "vX.Y.Z" }
Quick Start
use libfreemkv::{Drive, Disc, ScanOptions};
use std::path::Path;
// Open drive — identified via INQUIRY
let mut drive = Drive::open(Path::new("/dev/sg4"))?;
drive.wait_ready()?; // wait for disc
drive.init()?; // unlock + prep (handled internally)
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, internal unlock + prep, speed control, 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 a single unified pes::Stream trait (re-exported as PesStream) exposing read() and write() on one type. 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, with no external key file needed.
Blu-rays and UHD (AACS) require a keydb.cfg at ~/.config/freemkv/keydb.cfg (or passed via ScanOptions). No AACS key material is compiled into the binary.
Architecture
Drive — open, identify, init, single-shot read
├── ScsiTransport — SG_IO (Linux), IOKit (macOS), SPTI (Windows)
└── unlock_bridge — private seam to the freemkv-unlock crate
(firmware / AACS cert / CSS bus-auth unlockers)
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 (bus auth → player-key disc crack → known-plaintext title-key attack)
└── KEYDB — download + verify + save
Streams — unified PES pipeline
├── PesStream — pes::Stream: one trait, read()/write() PES frames
├── 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 |
| E9xxx | Stream / mux errors (URL, PES, ISO, pipeline, demux) |
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 capture your drive's identity for contribution. Drive-unlock profiles are maintained in the freemkv-unlock repository.
License
MIT