The bus-key gate only credited the cert handshake's read_data_key as proof
bus encryption was removed. A firmware unlocker removes it AT THE DRIVE
(serves clear content) and yields no read_data_key — so a SUCCESSFUL
firmware unlock (VID present, read_data_key None) tripped the gate and
blocked ALL key resolution, including the online source. That was the
root cause of live UHD discs reporting "missing keys" after an unlock.
Now a single predicate answers "is bus encryption gone?": never-had-it ||
file/ISO || firmware-unlocked || cert-bus-key. The gate is just
`if !bus_encryption_removed { error }` — no enumerated cases. HandshakeResult
gains `drive_unlocked`, and the read_data_key failure reason is captured so
the warn says WHY the bus key is missing.
Also: reword the first hardware-sense escalation as "fast-fail escalation"
(it is often transient — the drive recovers), reserving "wedge" for a
persistent run; and scrub the product name from core comments (it belongs
only in the unlocker crate).
540 lines
30 KiB
Markdown
540 lines
30 KiB
Markdown
# Changelog
|
|
|
|
## [1.2.0] — 2026-06-29
|
|
|
|
### Breaking
|
|
|
|
The disc's AACS version is now carried through the key-resolution path as the
|
|
single source of truth for the `Unit_Key_RO` stride (AACS-1.0 = 48-byte,
|
|
AACS-2.x = 64-byte), so keys are always read at the disc's own layout. That
|
|
threaded one new value through three public signatures. In-tree consumers
|
|
(`freemkv`, `autorip`, `freemkv-keysources`) are updated; external callers must
|
|
adjust:
|
|
|
|
- **`DiscInputs` gains a `version: u8` field** (between `volume_id` and `mkb`).
|
|
Code constructing it with a struct literal must add the field. It is normally
|
|
obtained from `Disc::inputs()`, not constructed by hand.
|
|
- **`keysource::DiscInputsCtx::new` takes one argument, not two** — the version
|
|
is now read from `inputs.version` (`new(inputs)` instead of
|
|
`new(inputs, version)`).
|
|
- **`disc::read_aacs_inputs` / `read_aacs_inputs_from_drive` return a 3-tuple**
|
|
`(inf, mkb, version)` instead of `(inf, mkb)`.
|
|
|
|
These are source-breaking for external crates.io consumers. Shipped under a
|
|
minor bump (1.2.0): libfreemkv's surface is not yet frozen and the only known
|
|
consumers are the in-tree toolchain crates.
|
|
|
|
### Added
|
|
|
|
- **Mux loss concealment — a logged gap still produces a decode-clean file.**
|
|
When a unit genuinely cannot be decrypted on the mux read path (a key the disc
|
|
never yielded, after the rip's own decrypt-verify already failed loud and
|
|
re-read), the mux no longer passes ciphertext downstream or emits a broken
|
|
frame. The undecryptable aligned unit is concealed as NULL transport-stream
|
|
packets (PID 0x1FFF, invisible to every real stream), and the codec layer
|
|
**drops forward to the next keyframe** so no frame with a dangling reference
|
|
reaches the muxer. An ffmpeg deep scan of the result is clean — no missing
|
|
references, no partial frames. The loss is tallied and logged, never silently
|
|
dropped, and the mux always completes. Audio and subtitle tracks have no
|
|
cross-frame references, so only the directly-affected frames are dropped there.
|
|
Decrypt-verify remains a **rip** gate (fail loud → re-read), never a mux gate.
|
|
|
|
### Changed
|
|
|
|
- **One hex parser.** All hex parsing (keys, IDs, key-source inputs) routes
|
|
through a single `libfreemkv::hex` parser instead of several ad-hoc decoders,
|
|
so length/odd-nibble/invalid-digit handling is identical everywhere.
|
|
- **Robust encrypted-unit sampling + a single MKB framing walker.** Up-front
|
|
AACS sampling tolerates content layouts that previously yielded too few
|
|
encrypted units to resolve a key, and the Media Key Block is now walked by one
|
|
framing routine shared across the in-band and out-of-band readers (no
|
|
divergent record-stride logic). AACS resolution hardened around these paths.
|
|
- **One reader, one `DiscInputs`.** `Disc::inputs()` is now the single, complete
|
|
source of a disc's AACS inputs (inf, MKB, VID, disc_hash, version), and
|
|
`read_aacs_inputs*` returns the version alongside inf+MKB. Both the CLI and
|
|
autorip resolve through `Disc::inputs()`; the duplicate out-of-band readers
|
|
(autorip's `key_files()`/`volume_id()`) and the stale mapfile-VID read are
|
|
removed. AACS file paths and the AACS major versions are now named constants
|
|
(`aacs::PATH_*`, `aacs::AACS_MAJOR_*`, `AacsVersion::major`/`from_major`) so a
|
|
fallback or stride change lives in exactly one place.
|
|
|
|
### Fixed
|
|
|
|
- **ISO mux no longer drops real video at content-fragment tails.** A title's
|
|
encrypted content can end mid-AACS-unit, with the disc zero-padding the rest
|
|
of the 6144-byte aligned unit to the next fragment. The decrypt-verify
|
|
demanded the TS sync byte on *all 32* source packets, so it rejected such a
|
|
tail unit over its legitimate padding — discarding the real video packets it
|
|
contained. On a flawless rip this surfaced as a small phantom "loss" at mux
|
|
(and, once retries were exhausted, a truncated MKV). Unit acceptance is now
|
|
**padding-aware**: only packets whose *source* (pre-decrypt) bytes are
|
|
non-zero must restore their TS sync; the zero padding is excluded from the
|
|
check and emitted as clean zeros. A full content unit still requires all 32
|
|
(unchanged — no wrong-key relaxation), and a unit whose *non-zero* tail fails
|
|
to decrypt is still rejected as a genuine bad read.
|
|
- **ISO online key resolution now sends the Media Key Block.** Capturing a
|
|
disc's AACS inputs at scan read the MKB with a full `read_file` of the
|
|
~128 MiB `MKB_RO`/`MKB_RW` allocation, which fails on file-backed readers —
|
|
leaving the MKB empty, so `Disc::inputs()` shipped `mkb=0` to an online key
|
|
service and the request was rejected (no key → no decrypt). Scan now reads the
|
|
MKB through the same bounded prefix-grow + trim reader as the out-of-band
|
|
path, so `Disc::inputs()` is the single complete source of AACS inputs — one
|
|
reader for every caller.
|
|
- **Read-time key-fetch parses `Unit_Key_RO.inf` at the disc's own AACS stride.**
|
|
The on-demand fetch (for a CPS unit not sampled up front) hardcoded the V20
|
|
64-byte stride, so an AACS-1.0 (V10) disc whose key arrived as a VUK derived
|
|
the wrong unit keys. `DiscInputs` now carries the disc's `version`, and the
|
|
context parses at the matching stride — the disc is the single source of truth
|
|
for its own stride (no separate version argument to drift).
|
|
- **A dry key-fetch for one unit no longer blocks fetching a different unit.**
|
|
A global "fetch spent" latch meant that once the key service returned nothing
|
|
for one CPS unit's ciphertext, no further unit was ever asked — so a multi-CPS
|
|
disc could strand a unit whose key the service *would* have served. Replaced
|
|
with a per-unit "already-asked-dry" set (still bounded by the fetch budget).
|
|
- **`verify::push_ranges` uses saturating arithmetic** so a corrupt-disc LBA near
|
|
`u32::MAX` can't panic (matches `udf::merge_ranges`).
|
|
- **Audio no longer corrupts at a stream discontinuity.** At a transport-stream
|
|
discontinuity — a continuity-counter break, an adaptation-field
|
|
discontinuity_indicator, or a concealed-loss gap — the AC-3 / DTS / TrueHD
|
|
parsers held a *truncated* partial access unit and spliced the post-gap bytes
|
|
onto it, manufacturing a corrupt frame (ffmpeg "exponent out of range" /
|
|
"Failed to decode block code(s)" / "Invalid data found") and, for TrueHD, a
|
|
non-monotonic timestamp band on multi-segment titles. The video path already
|
|
resynced via the keyframe gate; the audio parsers now do too — on a
|
|
discontinuity they drop the un-completable partial and resync on the next
|
|
syncword, rebasing the timestamp from the post-gap PES. A discontinuity becomes
|
|
a clean single-frame gap instead of a corrupt splice. Audio has no inter-frame
|
|
references, so dropping the truncated partial is the complete fix; the approach
|
|
matches FFmpeg's parser layer and GStreamer's `tsdemux`.
|
|
|
|
## [1.1.0]
|
|
|
|
### Added
|
|
|
|
- **Post-read decrypt-verify gate.** Every AACS unit read off the disc is now
|
|
buffered, re-aligned to its clip-file 6144-byte unit grid, and verified
|
|
(CPI flag → decrypt → strict all-32 TS-sync, matching libaacs `_verify_ts`)
|
|
before it is signed off as good. A unit that no held or freshly-fetched key
|
|
decrypts is treated exactly like a bad read — re-read by
|
|
the patch pass, terminal loss only if truly unrecoverable — closing the
|
|
"silent bad read" class where a sector reads OK but its ciphertext is subtly
|
|
wrong. **Fail-safe:** it only ever downgrades a unit it is *confident* is bad;
|
|
every uncertainty (no keys, a merely-missing key, an unread/zero-filled sector,
|
|
a non-AACS disc) leaves the read byte-for-byte as before. Gated by a
|
|
compile-time kill-switch (`POST_READ_VERIFY`), and container-pluggable (BD/UHD
|
|
transport stream today, with an HD-DVD program-stream seam in place).
|
|
- **Every error is now `Error: E<code> <message>`, with an Error Codes
|
|
reference.** User-facing errors show their code so you can look it up, and a
|
|
new **Error Codes** page lists every code with its message, cause, and next
|
|
steps. A contract test guarantees every error variant has a code, a message in
|
|
all seven languages, and a Codes-page entry. Messages are source-agnostic
|
|
("key source", never a specific database).
|
|
|
|
### Changed
|
|
|
|
- **AACS decrypt acceptance is now standards-strict.** A key is accepted only
|
|
when the decrypted unit has the TS sync byte on *all* 32 source packets
|
|
(libaacs `_verify_ts`), replacing a majority-vote heuristic where a wrong key
|
|
could coincidentally restore enough syncs to pass and silently corrupt a unit.
|
|
- keydb download/save moved out of the library into freemkv-keysources;
|
|
libfreemkv no longer has any keydb I/O (it already held no keys).
|
|
|
|
### Fixed
|
|
|
|
- **AACS content-certificate bus-encryption flag read from the wrong bit.** The
|
|
flag is bit 7 of byte 1 (libaacs `p[1] >> 7`) but was read as bit 0, so a
|
|
bus-encrypted disc parsed as *not* bus-encrypted — defeating the fail-loud
|
|
guard that refuses to decrypt bus-wrapped data to garbage when no bus key was
|
|
obtained. Also corrected the cc_id offset (byte 14) and the AACS2 type marker
|
|
(`0x10`). Confirmed against real retail content certificates.
|
|
|
|
- **DVD rips now start on the movie, not the disc menu.** A VTS title VOB's
|
|
start sector was read from the IFO as a VTS-relative pointer but used as an
|
|
absolute disc address, so a DVD title's read extents began `ifo_lba` sectors
|
|
too early — the rip opened on the disc's menu / VMGI region and only drifted
|
|
into the feature minutes later (Silence of the Lambs, for example, showed
|
|
several minutes of the main menu before the movie). The title VOB is now
|
|
rebased to its absolute on-disc location, so the rip begins at the first frame
|
|
of the feature. Aspect ratio and chapter timing were already correct; only the
|
|
starting sector was wrong. (Covered by a new absolute-placement regression
|
|
test.)
|
|
- **Container metadata correctness.** Unknown colorimetry now emits the CICP
|
|
"unspecified" code point (2) consistently across the MKV track and the FVI
|
|
sidecar (previously 0); PGS subtitle wipes use the NORMAL composition state
|
|
rather than a full epoch reset; and FVI source-byte offsets are written
|
|
within-sector per the format spec.
|
|
- **Multi-extent AACS alignment in `dir://` extraction.** AACS encrypts in
|
|
aligned units of 3 sectors (6 KiB), and the decrypt-on-read gate accepts a read
|
|
only when its LBA is unit-aligned against a base. The `dir://` file-tree
|
|
extractor set that base once, to the file's first extent. A fragmented file
|
|
(Long-AD / continuation-ICB allocation) has later extents starting at arbitrary
|
|
LBAs whose distance from the first extent is generally not a multiple of 3
|
|
sectors, so the first read of every later extent failed the gate, returned a
|
|
decrypt error, and the whole extent was written as a zero-filled hole — even
|
|
though the sectors were readable. The unit base is now re-anchored per extent
|
|
(matching the mux read paths), so each extent gates on its own unit grid.
|
|
Decryption math is unchanged. Same class as the rc.5.2 clip-anchor fix.
|
|
- **Distinct "no key" reasons.** When AACS key resolution has usable material
|
|
(device or processing keys) but cannot obtain the disc's Volume ID — needed to
|
|
derive the unit key — freemkv now reports a distinct "AACS Volume ID
|
|
unavailable" error (E7017) instead of collapsing it into the generic "no key"
|
|
error (E7022), which is now reserved for a genuine absence of any key material.
|
|
No key derivation or descramble logic changed — only the reason reported on a
|
|
resolution failure.
|
|
- **autorip keydb writes go to the right path.** Auto-download, daily refresh,
|
|
the "Update KEYDB" button, and the startup existence-check now resolve to the
|
|
service's config path (matching where reads look); they previously used the
|
|
CLI's executable-local default.
|
|
- **Crash-safety hardening** in `dir://` extraction and keydb writes (fsync of
|
|
files and parent directories around rename).
|
|
- **Windows-reserved filenames** (`CON`, `NUL`, `COM1`…) inside a disc's file
|
|
tree are safely renamed on extraction instead of aborting the walk.
|
|
- **`--version` now matches the build stamped into MKVs.** The CLI's `--version`
|
|
string and the `MuxingApp` / `WritingApp` fields written into every MKV now
|
|
derive from a single libfreemkv constant — the package version plus the git
|
|
short hash (e.g. `freemkv 1.1.0 (g835cc99)`). The muxer previously kept
|
|
its own copy of that string, so the two could drift; a binary and the files it
|
|
produces can no longer report different versions.
|
|
- **DTS-HD Master Audio: a false core-sync inside the lossless extension no
|
|
longer splits an audio frame.** A byte pattern in the extension substream that
|
|
resembled the `0x7FFE8001` core sync word could truncate the lossless
|
|
extension and produce decode errors on the affected frames. The extension
|
|
substream is now sized exactly from its header, so that pattern is skipped as
|
|
data.
|
|
- **TrueHD: decode timestamps no longer step backward.** In a case where the
|
|
source PES timing lagged the audio access-unit cadence, the muxed decode
|
|
timestamp could regress (non-monotonic-DTS warnings to the muxer); the running
|
|
timestamp is now clamped so it never goes backward.
|
|
|
|
### Tests
|
|
|
|
- 58 new tests across the toolchain (AACS key resolution, the unlocker seam, the
|
|
key sources, DVD/CSS, `dir://` routing, and autorip keydb resolution).
|
|
|
|
## [1.0.0-rc.5.3]
|
|
|
|
### Added
|
|
|
|
- **`dir://` output** — write a decrypted `VIDEO_TS` / `BDMV` file tree straight
|
|
from a disc or ISO instead of a single muxed file.
|
|
|
|
### Changed
|
|
|
|
- **Source-agnostic key errors** — decryption messages no longer assume a local
|
|
key database is *the* key source.
|
|
- **The default `keydb.cfg` location is next to the executable** (portable CLI);
|
|
the autorip service keeps its container path.
|
|
- **Simpler flags** — dropped `-k` (use `--keydb`) and removed `--device` (the
|
|
drive is named in the source URL, e.g. `disc:///dev/sgN`).
|
|
|
|
### Fixed
|
|
|
|
- **Fail loud on missing keys or bad input** instead of silently writing an
|
|
undecrypted file.
|
|
|
|
## [1.0.0-rc.5.2]
|
|
|
|
### Fixed
|
|
|
|
- **Reverted the rc.5.1 `DefaultDecodedFieldDuration` experiment for interlaced
|
|
SD-DVD.** rc.5.1 added a 20 ms `DefaultDecodedFieldDuration` field element to
|
|
the 576i/480i track header on the theory that Windows derives fps from it.
|
|
Captured evidence showed that element made Windows Explorer report 12.5 fps
|
|
(half) and MediaInfo flip the track to "Frame rate mode: Variable", while
|
|
MakeMKV's rip of the same disc omits it. The element is therefore no longer
|
|
written (`MkvTrack::video` now passes `field_duration_ns == 0`); the track
|
|
keeps `FlagInterlaced=1` + `FieldOrder=TFF` and the full-frame 40 ms
|
|
`DefaultDuration` (`1/DefaultDuration` = 25 fps), matching MakeMKV. How a given
|
|
player or shell handler chooses to display interlaced fps is not guaranteed.
|
|
- **Correct AC-3 audio track selected on DVDs with non-standard sub-stream
|
|
ordering.** freemkv assigned each declared audio stream a physical sub-stream
|
|
by ordinal (`0x80+n`), assuming the IFO's first stream lives at `0x80`. On
|
|
discs where the 5.1 main mix sits on a different sub-stream and `0x80` carries
|
|
a 2.0 down-mix (e.g. Silence of the Lambs), the 2.0 was muxed under a "5.1"
|
|
label. freemkv now probes each physical sub-stream's actual channel count from
|
|
the disc — scanning every AC-3 frame and taking the maximum, so a brief 2.0
|
|
logo bed at the feature head can't mask the real 5.1 — and routes each declared
|
|
stream onto the sub-stream that genuinely matches.
|
|
- **"Decryption failed" on large AACS Blu-ray titles fixed.** AACS encrypts in
|
|
aligned units of 3 sectors (6 KiB); the unit-alignment gate measured `lba % 3`
|
|
against absolute disc LBA 0, but the unit grid is actually anchored at each
|
|
clip's encrypted-region start. A clip whose start is not 3-sector-aligned had
|
|
its readable units wrongly rejected — failing the feature/large titles of some
|
|
discs while short clips passed. The gate is now clip-anchored.
|
|
- **Single-pass disc→MKV recovers marginal/transient sectors before failing.**
|
|
The direct-to-MKV path now gives the drive its full ECC recovery budget on a
|
|
bad sector (matching the multipass rip) instead of reporting a read failure a
|
|
multipass rip would have recovered.
|
|
- **4K decode glitches at non-seamless clip joins fixed (Top Gun class).**
|
|
Titles assembled from clips joined at non-seamless boundaries no longer drop
|
|
reference frames at the join ("Could not find ref" stutter); the splice
|
|
keyframe is rewritten so the decoder discards only the genuinely-dangling
|
|
leading pictures.
|
|
|
|
### Changed
|
|
|
|
- **`freemkv-keysources` is now a pure key lookup.** The encrypted content-sample
|
|
reader and the candidate-key resolution loop moved into libfreemkv (they read
|
|
the disc and validate keys — decryption mechanism, not lookup). A key source
|
|
now only looks a key up and hands it back. Downstream API: use
|
|
`libfreemkv::read_encrypted_units` / `libfreemkv::resolve_and_apply` (was
|
|
`freemkv_keysources::read_sample_units` / `…::resolve_and_apply`).
|
|
|
|
### Added
|
|
|
|
- **`--log-level 3` is now self-sufficient for MKV/opening-frame diagnosis.**
|
|
The diagnostic pass now (a) dumps the ACTUAL MKV `TrackEntry` elements written
|
|
per track (`tag=mkv.track`: FlagInterlaced, FieldOrder, DefaultDuration,
|
|
DefaultDecodedFieldDuration via field-duration, Display dims, codecPrivate as
|
|
hex) so the Windows-fps-class metadata is verifiable from a log alone, and
|
|
(b) captures the first ~100 coded frames per track (raw bytes) to a
|
|
`<output>.opening.bin` side file with a per-frame summary line
|
|
(`tag=mkv.opening.frame`: track, key/delta, size, PTS) so opening-GOP / menu
|
|
issues are diagnosable from a future log without the disc. Both are gated to
|
|
log-level 3; a normal run opens no side file and records nothing.
|
|
|
|
### Verified
|
|
|
|
- **DVD opening-GOP / still-frame open handling is correct (no change needed).**
|
|
The hypothesis that the opening pictures get the wrong (last-seen) sequence
|
|
header or have their PTS floored to t=0 was traced and ruled out: the
|
|
codecPrivate is the FIRST sequence header (read once at headers-ready, before
|
|
any later AU), DVD VOBU structure guarantees each title opens on a sequence
|
|
header + I-frame (no mid-GOP open), the parser back-anchors leading
|
|
still-frames to the disc's real timeline, and the muxer anchors its timestamp
|
|
base on the opening keyframe's real PTS so the t=0 floor never corrupts it.
|
|
Regression tests pin all three.
|
|
|
|
## [1.0.0-rc.5.1]
|
|
|
|
### Fixed
|
|
|
|
- **CSS reads unlocked on enforcing drives.** CSS-protected DVDs on
|
|
drives that enforce CSS authentication previously produced an empty MKV
|
|
at exit 0, or hung indefinitely. The read path now issues the bus-auth
|
|
handshake (`css::auth::unlock_css_reads`) to unlock scrambled-sector
|
|
reads before attempting any data transfer, so the drive gates lift
|
|
correctly.
|
|
- **Keyless title-key recovery always runs.** The Stevenson known-plaintext
|
|
attack (`css::crack_key` / `src/css/stevenson.rs`) now recovers the
|
|
title key even when the bus-auth scan detects a CSS drive, removing a
|
|
code path that fell through to locked reads on certain disc/drive
|
|
combinations. A wrong key still fails cleanly (confirmed by a sector
|
|
descramble check) rather than producing silent garbage.
|
|
- **Early bail on undecryptable discs.** When CSS authentication succeeds
|
|
but no valid title key can be recovered, the mux path now terminates
|
|
with a clear error code instead of writing an empty (or zero-byte)
|
|
output file.
|
|
- **DVD audio channel count from AC-3 bitstream.** The audio channel count
|
|
is now parsed from the AC-3 elementary-stream bitfield rather than from
|
|
the IFO audio attributes, so the reported channel count always matches the
|
|
actual muxed audio even when the IFO attribute disagrees. Passthrough only
|
|
— no downmix is performed. (Selecting the correct audio sub-stream on discs
|
|
with non-standard ordering is a separate item — see Known issues.)
|
|
- **Interlaced MKV frame rate on Windows.** Interlaced content (576i/480i)
|
|
now emits a `DefaultDecodedFieldDuration` element in the MKV track
|
|
header, which Windows Media Foundation and Explorer use to derive the
|
|
display frame rate. Without it, players reported an incorrect or zero
|
|
frame rate on interlaced tracks.
|
|
- **Per-track `BPS` bitrate tags populated.** The `BPS` tag is written for
|
|
each track so players and shell extensions (Windows Explorer, MPC-HC,
|
|
etc.) can display the per-stream bitrate without reading the full file.
|
|
- **Interlaced field order corrected to TFF.** 576i tracks were written
|
|
with a bottom-field-first (BFF) container flag that disagreed with the
|
|
top-field-first order carried in the MPEG-2 stream; the MKV `FieldOrder`
|
|
element now matches the stream (TFF) so deinterlacers use the correct
|
|
field parity.
|
|
- **DVD first-play menu no longer prepended to the feature.** The title
|
|
VOBS base sector was read from the VTS menu-VOBS pointer (`vtsm_vobs`,
|
|
offset 0xC0) instead of the title-VOBS pointer (`vtstt_vobs`, 0xC4), so on
|
|
a disc that authors a per-title menu the entire menu VOB — e.g. a studio
|
|
first-play "the parental level has been set, press yes" prompt — was
|
|
prepended to the movie and every cell extent shifted back. The rip now
|
|
opens on the feature's first frame.
|
|
|
|
### Changed
|
|
|
|
- **AACS handshake skipped on DVDs.** The AACS authentication sequence is
|
|
no longer attempted on DVD discs (it never applied to CSS-encrypted
|
|
media); attempting it on a DVD drive was a no-op at best and surfaced
|
|
spurious errors at worst.
|
|
|
|
### Added
|
|
|
|
- **Structured disc diagnostics at `--log-level 3`.** A new diagnostic
|
|
pass emits structured log events at INFO level when the log level is 3
|
|
or higher: DVD PGC/cell layout and IFO video/audio attributes; BD/UHD
|
|
playlist, clip, and AACS metadata. Provides a single-command snapshot
|
|
for diagnosing mux or authentication issues without instrumenting the
|
|
source.
|
|
- **Reduced per-operation log spam.** Mux-read and seek operations are
|
|
demoted to TRACE (were DEBUG); benign navigation-packet drops are
|
|
summarized as a single counter at the end of the title rather than
|
|
logged per-packet.
|
|
|
|
### Known issues
|
|
|
|
- **Wrong audio track on discs with non-standard substream ordering.**
|
|
Audio sub-stream ids are assigned by per-codec ordinal rather than read
|
|
from the IFO/PGC stream-number table, so a disc whose physical substream
|
|
order diverges from the convention may select the wrong audio track
|
|
(e.g. a 2.0 stream in place of 5.1). Diagnose with
|
|
`freemkv info disc://… --log-level 3`; fix tracked for the next release.
|
|
|
|
## [1.0.0-rc.4.2]
|
|
|
|
### Fixed
|
|
|
|
- **Windows durability.** New platform-aware `io::fsync` module: directory
|
|
fsync is a no-op on Windows (std cannot open a directory there, which
|
|
logged a spurious warning on every mapfile write — including from the
|
|
CLI), and a shared `file_durable` helper opens files read+write before
|
|
`sync_all` so the flush succeeds on Windows, where `FlushFileBuffers`
|
|
rejects a read-only handle with `ERROR_ACCESS_DENIED`.
|
|
|
|
## [1.0.0-rc.4] — UNRELEASED
|
|
|
|
An audit-driven round of correctness, durability, and Windows-transport
|
|
fixes. No API changes; behavior is more conservative on damaged media and
|
|
on partial decryption.
|
|
|
|
### Fixed
|
|
|
|
- **Decrypt-time loss is accounted for.** A partial AACS/CSS decryption
|
|
failure can no longer pass as a perfect rip — skipped/undecryptable
|
|
bytes are folded into the loss total — and partial CPS-unit (per-title)
|
|
key coverage is rejected in the AACS validation gate instead of
|
|
producing partly-garbage output.
|
|
- **Durable writes.** `keydb.cfg` is written atomically (temp file +
|
|
fsync + rename), and the mapfile fsyncs its parent directory after the
|
|
rename so a resume checkpoint survives a crash.
|
|
- **Truthful error causes.** A server-dropped keydb download is
|
|
classified as a connection error, not a parse error; a missing home
|
|
directory maps to "not found" rather than a keydb-parse failure; the
|
|
I/O error from opening an AACS-inputs ISO is preserved; and a
|
|
transport failure is preserved through the AACS auth handshake instead
|
|
of being relabeled.
|
|
- A failed `READ CAPACITY` now warns instead of silently using a
|
|
zero-sector disc.
|
|
- A leaked pipeline consumer can no longer finalize an abandoned output.
|
|
- **Windows SCSI.** `ScsiPassThroughDirect` is packed to match the
|
|
`ntddscsi.h` layout, `StorageAdapterDescriptor.BusType` width is
|
|
corrected (`u8` → `u32`), oversized read batches on non-sysfs
|
|
(Windows) drives are bounded, `IOCTL_STORAGE_RESET_DEVICE` failures are
|
|
surfaced, and a device reset only sleeps on success.
|
|
- Mux now tracks skipped bytes so a partly-read title reports accurate
|
|
loss.
|
|
|
|
### Changed
|
|
|
|
- The per-read `Drive::read` trace event was demoted to TRACE so a debug
|
|
log isn't flooded by per-sector reads.
|
|
|
|
## [1.0.0-rc.2]
|
|
|
|
Second release candidate for 1.0. libfreemkv is the core library: disc scan,
|
|
multipass sector recovery, content decryption (CSS, AACS 1.0/2.0), and the
|
|
threaded mux pipeline that turns a disc or ISO into an MKV. This candidate adds
|
|
keyless DVD/CSS support and correct DVD video, on top of security and recovery
|
|
hardening.
|
|
|
|
### Added
|
|
|
|
- **Keyless DVD/CSS title-key recovery.** A CSS-protected DVD decrypts with no
|
|
key database — the title key is recovered directly from the scrambled disc
|
|
data via the Stevenson known-plaintext attack (ported from libdvdcss) and
|
|
validated by descrambling a sector and confirming the known plaintext
|
|
reappears, so a wrong key fails cleanly instead of producing silent garbage
|
|
(`src/css/stevenson.rs`). `Disc::scan_image` recovers the same title key from
|
|
a raw, still-scrambled CSS ISO, so a raw image can be muxed without
|
|
pre-decryption.
|
|
- **MPEG-2 Program-Stream access-unit reassembler** (`src/mux/codec/mpeg2.rs`).
|
|
Buffers elementary-stream bytes across PES packets and emits exactly one
|
|
coded picture per MKV block, with presentation timestamps reconstructed from
|
|
the stream — fixing corrupted DVD video. Bounded buffer so a malformed stream
|
|
cannot exhaust memory.
|
|
|
|
### Changed
|
|
|
|
- Self-contained keyframes: the active param sets (HEVC VPS/SPS/PPS, H.264
|
|
SPS/PPS, VC-1 sequence/entry headers) are re-asserted at every keyframe and
|
|
any mid-title param-set change is emitted in-band, fixing whole-segment
|
|
HEVC/H.264/VC-1 corruption when a source stops repeating or reverts a param
|
|
set.
|
|
- Block timestamps use presentation order keyed on track type, so B-frame video
|
|
(including a Dolby Vision enhancement layer) keeps its true presentation
|
|
timestamps instead of decode-order timecodes.
|
|
- Mux unit alignment is scheme-aware (AACS vs CSS/none), so DVD extents are no
|
|
longer rejected for unit misalignment.
|
|
- MKV output records `freemkv <version>` in the Muxing/Writing application
|
|
fields, so every output file is traceable to its build.
|
|
- Subtitle `BlockDuration` values are scaled by the segment timecode scale, so
|
|
display durations are correct when the scale is not 1 ms.
|
|
- The NOT_READY retry pause in the patch (Pass N) loop is halt-responsive: a
|
|
stop request interrupts the drive-recovery wait immediately instead of
|
|
blocking shutdown.
|
|
- Bounded the keydb decompressed-plaintext reader (caps a malformed or
|
|
zip-bombed download).
|
|
|
|
### Fixed
|
|
|
|
- A `READ(10)` that returns GOOD status with a residual underrun is treated as a
|
|
failed read (routed to retry) instead of committing stale buffer data —
|
|
closing a silent-corruption hole in the sweep and patch paths.
|
|
- `raw_command` on Linux masks the `DRIVER_SENSE` bit before treating a result
|
|
as an error, preventing false transport errors on commands that return sense
|
|
alongside a GOOD response.
|
|
- `READ CAPACITY (10)` rejects the "capacity exceeds 32-bit" sentinel instead of
|
|
silently wrapping to 0 and misreporting disc size.
|
|
|
|
### Security
|
|
|
|
- Content keys (CSS disc/title keys, AACS unit/volume keys) are redacted in log
|
|
output (logged as `<redacted>` with a 1-byte fingerprint); a test guards
|
|
against any key field being logged with a raw value.
|
|
- The macOS SCSI shim uses `posix_spawn` directly instead of `system()` / `sh
|
|
-c`, eliminating a command-injection vector on the device-path string.
|
|
|
|
## [1.0.0-rc.1]
|
|
|
|
First release candidate for 1.0 — the first tagged 1.0 milestone of the core
|
|
library. Established the full feature set: multipass sector recovery, content
|
|
decryption (CSS, AACS 1.0/2.0) from `keydb.cfg`, disc parsing, and the threaded
|
|
mux pipeline (see "Pre-1.0 development" for the consolidated feature list).
|
|
|
|
## Pre-1.0 development
|
|
|
|
Versions 0.x were the iterative development series leading up to 1.0. The
|
|
highlights, condensed:
|
|
|
|
- **Multipass recovery engine.** Pass 1 sweeps the whole disc sequentially,
|
|
tolerating bad sectors with an adaptive damage-jump algorithm (mark the bad
|
|
range, keep going). Pass N retries the bad ranges with per-sector recovery
|
|
timeouts, reverse-direction reads, and range bisection. A mapfile tracks
|
|
per-sector state across passes so a rip can resume.
|
|
- **Drive and SCSI layer.** Single-shot, synchronous SG_IO transport on Linux
|
|
(with IOKit on macOS and SPTI on Windows), full SCSI sense decoding, and
|
|
drive enumeration / presence probes. Single-shot reads by design — recovery
|
|
lives in the multipass orchestration, not inline in the read path.
|
|
- **Content decryption.** CSS for DVDs and AACS 1.0/2.0 for Blu-ray and UHD,
|
|
with keys read from `keydb.cfg`. A single decrypting decorator wraps the
|
|
sector source so decryption is one audited surface, and a resolved key is
|
|
verified against disc content before it is applied.
|
|
- **Disc parsing.** UDF, MPLS/CLPI (Blu-ray), and IFO (DVD) parsing for title
|
|
and extent assembly, with bounds checks on values derived from untrusted disc
|
|
input. Canonical main-title selection picks the real feature over a
|
|
play-all virtual playlist on branching discs.
|
|
- **Mux pipeline (the "highway").** A three-stage threaded pipeline —
|
|
read+decrypt, demux, codec parse — with a recycled buffer pool, taking
|
|
file-backed mux from ~60 MB/s to several hundred MB/s warm-cache. Codec
|
|
parsers for HEVC, H.264, VC-1, MPEG-2, TrueHD, DTS(-HD), and PGS feed an
|
|
EBML/Matroska writer.
|
|
- **I/O stack.** Bounded-cache writeback (`sync_file_range` +
|
|
`posix_fadvise(DONTNEED)`) keeps the kernel dirty-page cache bounded on long
|
|
sequential writes, and time-batched mapfile persistence keeps NFS-staged rips
|
|
fast.
|
|
- **Library hygiene.** No user-facing English in the library — all errors are
|
|
numeric codes handled by the application layer. A large spec-grounded,
|
|
mutation-verified test suite guards the silent-corruption surfaces. Rust 2024
|
|
edition; release builds use thin LTO.
|