53 Commits
Author SHA1 Message Date
Matthew Jackson 28a6e50ede 1.6.0: version sync + fix stale docs
Bump to 1.6.0 (workspace sync, no functional change). Fix the README
example that called the removed matches()/unlock() API — rewrite to the
real unlock_features()/unlock_bus() contract (+ ld/README).
2026-07-28 15:35:19 -07:00
Matthew Jackson 93588379c6 Rename the DVD read-unlocker: CSS -> DVD
The unlocker performs the DVD bus-auth that clears the drive's
scrambled-read barrier, letting a protected DVD be read at all. That is
a property of the DVD medium, not of the CSS scheme — the descramble key
is recovered keylessly downstream — so naming it "CSS" conflated the
read-enable with the encryption. Rename the struct to DvdUnlocker, its
registry name to "DVD", and the tests to match. Bump 1.5.2.
2026-07-22 13:48:56 -07:00
Matthew Jackson cc4c9e496d v1.5.1: bump version (unified release) 2026-07-20 17:02:02 -07:00
Matthew Jackson 4a6f0dee19 v1.5.0: bump version (unified release) 2026-07-19 15:37:17 -07:00
Matthew Jackson 93988bebba v1.4.5: bump version (unified release) 2026-07-18 16:07:53 -07:00
Matthew Jackson c5cf5e6a0c v1.4.4: bump version (unified release) 2026-07-16 21:43:57 -07:00
Matthew Jackson 71380a0b26 v1.4.3: bump version (unified release) 2026-07-16 21:05:53 -07:00
Matthew Jackson f1cedd63d9 v1.4.2: bump version (unified release) 2026-07-15 19:37:43 -07:00
Matthew Jackson fb7d74039f v1.4.1: bump version (unified release) 2026-07-14 14:45:21 -07:00
Matthew Jackson aa82e9cf6b Changelog for 1.4.1 2026-07-14 14:43:14 -07:00
Matthew Jackson d32d43c2e2 v1.4.0: bump version (unified release) 2026-07-13 19:28:44 -07:00
Matthew Jackson eb35571632 Changelog: 1.4.0 (Blu-ray 3D / MVC) 2026-07-13 12:47:03 -07:00
Matthew Jackson 075352158c ld: restore module README with LibreDrive credit to Mike Chen + the MakeMKV team 2026-07-10 14:35:44 -07:00
Matthew Jackson 9fbbe15b29 1.3.2: version sync
Inherits libfreemkv 1.3.2.
2026-07-10 14:00:35 -07:00
Matthew Jackson b7f34c46bc 1.3.1: relicense to MIT
Relicensed from AGPL-3.0 to MIT, effective 1.3.1 (<=1.3.0 remain AGPL).
Version sync to 1.3.1.
2026-07-10 12:31:19 -07:00
Matthew Jackson 4120d38151 docs: add 1.3.0 changelog 2026-07-10 08:19:22 -07:00
Matthew Jackson ca8119a167 renesis: detection-only stub; drop unverified vendor command 2026-07-09 16:36:47 -07:00
Matthew Jackson e0bf264538 release: bump to 1.3.0 2026-07-08 14:48:50 -07:00
Matthew Jackson d07d8f0dac unlock: split Unlocker into unlock_features/unlock_bus; add Renesas unlocker
Replace the single matches()+unlock() contract with two capability methods — unlock_features (drive riplock/speed/OEM VID at drive-prep) and unlock_bus (AACS/CSS bus-encryption removal for the mounted disc) — each defaulting to NotApplicable so an unlocker implements only what it does.

Add the renesis module: the Renesas-platform unlocker (Pioneer + HL-DT-ST Renesas), detected via the READ_BUFFER 0x02/0xF1 identity probe (ASCII "SAT" marker). Features only; the cert handles the bus.

Add product_id to DriveId. Bump to 1.2.3.
2026-07-07 16:35:57 -07:00
Matthew Jackson a96b675e7a v1.2.2: bump version (unified release) 2026-07-03 20:50:05 -07:00
Matthew Jackson a64e82ad4f v1.2.1: bump version 2026-07-01 17:30:25 -07:00
Matthew Jackson 892e586340 unlock: drop applies_to — the matrix reports did-work, not matched 2026-07-01 10:44:39 -07:00
Matthew Jackson 569e23662b unlock: add Unlocker::name() + report-only applies_to() for the matrix
Each unlocker now carries its own stable name() (the single source; apps
never hardcode it) and a report-only applies_to() — phase-independent
'would this apply to this drive+disc', distinct from the dispatch-gated
matches(). LibreDrive overrides applies_to to report on drive identity
regardless of disc kind (its matches() is gated to the Unknown-kind init
phase). Enables a registry-driven unlocker matrix.
2026-07-01 10:00:59 -07:00
Matthew Jackson 852cee1ea0 Revert "unlock: drive-features capability (stock riplock lift for any disc)"
This reverts commit 00e56dbb7d.
2026-07-01 09:34:58 -07:00
Matthew Jackson 00e56dbb7d unlock: drive-features capability (stock riplock lift for any disc)
Reframe: an unlocker provides drive-features (speed/riplock — a property
of the DRIVE, any disc) AND bus removal (AACS decrypt+VID / CSS), kept
separate so a disc can take one without the other. A CSS DVD wants a
matched drive's speed but must NOT take its firmware bus-unlock (breaks
stock CSS).

Add Unlocker::apply_drive_features (default no-op, self-gating). LibreDrive
implements it with STOCK MMC commands only — SET STREAMING (0xB6), the
modern riplock lift many slot-loading BD combos honor when they ignore the
legacy SET CD SPEED (0xBB), then SET CD SPEED as fallback — so it is safe
on a non-unlocked drive. New build_set_streaming() + tests.
2026-07-01 08:56:10 -07:00
Matthew Jackson df32c1af20 README: rewrite for the single-crate, base-of-graph architecture 2026-06-29 21:15:04 -07:00
Matthew Jackson 83734562eb Cargo: publish = false (firmware crate — git-tag-only, never crates.io) 2026-06-29 21:04:05 -07:00
Matthew Jackson 30f653f7f4 ld: public profile catalog API + emulation feature; clippy-clean
Expose the LibreDrive profile catalog as a curated public API on `ld`:
`ld::profiles()` returns the `Profiles` object (with `.get(drive_id)`) and
`ld::profile(filter)` finds one — the catalog of supported drives, queried
without unlocking. The unlock mechanism (firmware blobs, upload sequence, CDB
wire format) stays private; `LibreDrive` is pub(crate), reached only via
`all_unlockers()`.

The unlock-handshake wire format the bdemu test-emulator needs to impersonate a
drive (`UNLOCK_MARKER`, `is_unlock_read_buffer`) is behind a non-default
`emulation` feature; the `cdb` module compiles only under it. Rename the
`ScsiError` error variant to `Scsi` (matches css), gate the test-only
`load_bundled` and `SCSI_STATUS_CHECK_CONDITION`. Clippy-clean in both feature
configs; 86/89 tests pass.
2026-06-29 20:44:52 -07:00
Matthew Jackson 5ea0e2cc5a ld: drive-prep guard (fire only at kind==Unknown) + firmware_unlocker_name
Restore ld's drive-prep match guard so the firmware unlocker fires exactly once
(at drive-prep, kind==Unknown) and never re-fires during the content-keyed
Aacs/Css dispatch — the consumer (libfreemkv) dispatches at three points
(init/cert/css) with the appropriate kind. Add firmware_unlocker_name(drive_id)
for "is this drive supported?" drive-info display without unlocking. 89 tests.
2026-06-29 19:54:36 -07:00
Matthew Jackson 02d50664ff css: relocate the CSS bus-auth into freemkv-unlock; all 3 unlockers wired
Stage 3: the CSS bus-authentication (the challenge-response that sets the
drive's ASF=1 to unlock scrambled-sector reads, plus crypt_key + the CSS
tables) moves out of libfreemkv into the self-contained src/css module, with
its own error type. CssUnlocker impls crate::Unlocker — matches DiscKind::Css,
self-guards against a non-DVD profile, runs the bus-auth, and returns an empty
Unlocked (CSS yields no VID/bus key; the keyless Stevenson descramble stays in
libfreemkv). The keyless descramble (lfsr/stevenson) is NOT moved — that's
content decryption, the consumer's job.

all_unlockers() now returns [LibreDrive, AacsCert, CssUnlocker] — the entire
unlock layer is built. libfreemkv rewires onto it in stage 4. 89 tests pass.
2026-06-29 19:36:28 -07:00
Matthew Jackson 22130bfe12 aacs: relocate the AACS cert handshake into freemkv-unlock
Stage 2: the AACS host-certificate handshake (the AKE + bus-key derivation +
P-160/P-256 EC crypto, ~2050 lines) moves out of libfreemkv into the
self-contained src/aacs module, with its own error type (the Aacs* failure
points + structured ScsiError) and an aes_ecb_decrypt helper. AacsCert impls
crate::Unlocker — matches DiscKind::Aacs, runs run_cert_handshake against the
host certs the consumer passes via UnlockCtx, and returns Unlocked { vid,
bus_key }. collect_host_certs stays in libfreemkv (it reads keysources). The
SCSI contract gains ScsiSense + the AACS/REPORT-KEY opcodes. libfreemkv is
untouched (still green); it rewires onto this in stage 4.

72 tests pass (the handshake brought its full EC-crypto test suite).
2026-06-29 19:33:07 -07:00
Matthew Jackson 0e943c3792 seam cleanup: /src generic-only; drop set_max from the trait
Per the seam audit: keep /src purely the generic contract and move everything
unlocker-specific into its module.

- ld's error type moves src/error.rs -> src/ld/error.rs; it owns the conversions
  From<scsi::ScsiError> (transport fault in) and From<ld::Error> for UnlockError
  (outcome out). src/scsi.rs keeps only the generic transport contract + a
  generic ScsiError; the trait no longer references any concrete error.
- Remove set_max_read_speed from the Unlocker trait — raising read speed is
  drive tuning, NOT bus removal. libfreemkv will issue a generic SET CD SPEED
  itself at drive-open (tracked).

29 tests pass.
2026-06-29 19:19:19 -07:00
Matthew Jackson 314c365290 collapse freemkv-unlock into one crate; ld becomes a module
Replace the ld/aacs/css member-crate workspace with a single freemkv-unlock
crate: the generic Unlocker contract + SCSI transport contract at the crate
root (lib.rs, scsi.rs, error.rs), and the firmware unlocker as the self-
contained src/ld module. The contract is repo-agnostic raw types (DriveId,
HostCert, DiscKind, Unlocked, UnlockError) with NO libfreemkv dependency, so
libfreemkv will depend on this crate (one-way, no cycle) and dispatch via
all_unlockers(). ld now impl crate::Unlocker, takes its own DriveId (4 raw
fields, no INQUIRY parsing), and returns a raw Option<[u8;16]> VID. The old
aacs/css plugin wrappers are removed; their crypto moves in from libfreemkv in
the next stages. 31 tests pass.
2026-06-29 19:11:21 -07:00
Matthew Jackson 58a4b9720f aacs: new freemkv-unlock-aacs plugin crate
The AACS host-certificate bus-auth unlocker as a first-class external plugin,
peer to freemkv-unlock-ld and freemkv-unlock-css. AacsUnlocker impls
libfreemkv::Unlocker — matches DiscKind::Aacs, self-guards against the hardware
(refuses NotApplicable without issuing a handshake CDB if the drive reports a
non-Blu-ray profile), gathers host certs from the scan options, and runs the
libfreemkv cert-handshake primitive to learn the Volume ID + bus key. libfreemkv
keeps the handshake + AACS content decryption; this crate owns the unlocker.
Additive: a consuming binary opts in with one register_unlocker(...) line.
2026-06-29 17:33:27 -07:00
Matthew Jackson 26a2859eb4 css: new freemkv-unlock-css plugin crate
The CSS (DVD-Video) bus-auth unlocker as a first-class external plugin, peer
to freemkv-unlock-ld. CssUnlocker impls libfreemkv::Unlocker — matches
DiscKind::Css, self-guards against the hardware (refuses NotApplicable without
issuing a CSS CDB if the drive reports a non-DVD profile), then runs the
libfreemkv bus-auth primitive (unlock_css_reads). libfreemkv keeps the bus-auth
+ keyless descramble; this crate owns the unlocker impl. Additive: a consuming
binary opts in with one register_unlocker(CssUnlocker::new()) line; the in-tree
built-in stays until clients are rewired.
2026-06-29 17:22:50 -07:00
Matthew Jackson 90fbf4164e unlock: firmware Unlocked sets drive_unlocked=true
The firmware route puts the drive into clear-content mode, so it reports
drive_unlocked=true (and no bus key) per the extended Unlocked contract.
2026-06-29 16:59:04 -07:00
Matthew Jackson a7745ad931 unlock: firmware unlocker matches only the drive-prep phase
LibreDrive::matches now requires ctx.kind == DiscKind::Unknown (the drive-prep
dispatch) in addition to a bundled-profile hit. This keeps the firmware unlock
from firing during the later content-keyed (Aacs/Css) dispatch on a profiled
drive — the registry is walked at one spot per phase and matches() is the only
filter.
2026-06-29 16:49:52 -07:00
Matthew Jackson a79dd55b3f unlock: adopt UnlockCtx in the Unlocker impl
libfreemkv's Unlocker trait now hands matches()/unlock()/set_max_read_speed()
an UnlockCtx instead of a bare DriveId. The firmware unlocker keys off
ctx.drive_id (disc kind is irrelevant to it).
2026-06-29 16:27:30 -07:00
Matthew Jackson 545d2c0c73 unlock: return Unlocked per the generalized libfreemkv trait
libfreemkv's Unlocker::unlock now returns Unlocked { vid, read_data_key }
rather than a bare Vid. The firmware route serves clear content, so it
reports { vid: Some(oem_vid), read_data_key: None }.
2026-06-29 15:09:22 -07:00
Matthew Jackson b8aa3e68de v1.1.0: bump version (unified release) 2026-06-28 18:20:14 -07:00
Matthew Jackson f0a9d618eb Unlocker reshape: unlock() -> Result<Vid, UnlockError>
LibreDrive impl migrated to the single unlock() returning the OEM VID;
firmware-unsupported -> FirmwareNotUnlockable, no-VID -> VidUnavailable.
2026-06-26 12:19:24 -07:00
Matthew Jackson 165da09062 v1.0.0-rc.5.3: bump version (unified release) 2026-06-24 21:55:19 -07:00
Matthew Jackson 3d4567a9ec v1.0.0-rc.5.2: bump version (unified release) 2026-06-24 17:19:59 -07:00
Matthew Jackson 50efc5f416 v1.0.0-rc.5.1: bump ld version + libfreemkv dep (exact) 2026-06-24 14:47:16 -07:00
Matthew Jackson 9f4cf7f9ea Add Contributor Covenant v2.1 Code of Conduct 2026-06-24 10:45:07 -07:00
Matthew Jackson 2b5a71422a v1.0.0-rc.5: bump ld version + libfreemkv dep (exact) 2026-06-24 10:41:57 -07:00
Matthew Jackson 8a5ab9f27d Fix rc5 audit findings: exact libfreemkv pin, cdb test coverage
- ld/Cargo.toml: pin libfreemkv = "=1.0.0-rc.4.3" (exact, fail-closed)
  instead of the bare caret that accepted later 1.0.0 prereleases.
- ld/src/cdb.rs: replace the duplicate (2,0x44) assertion with a real
  wrong-mode/right-buffer case (0,0x77) so the 'Wrong mode, right buffer
  id' comment is actually exercised.
2026-06-23 19:11:25 -07:00
Matthew Jackson ef3a6fe42d ld: require libfreemkv 1.0.0-rc.4.3 exactly (fail-closed, not caret floor) 2026-06-23 15:49:55 -07:00
Matthew Jackson 15e263451e mt1959_b: per-drive fw_verify_cdb + correct firmware length; v1.0.0-rc.4.3
The variant-B firmware load hardcoded one drive's F1 vendor-verify token
and a fixed 0x9C0 firmware size. Across the 140 B drives the verify CDB
has 39 distinct per-drive values, and the real firmware length is encoded
in each drive's MODE SELECT CDB (2192..2528 bytes) — so the constants were
wrong for ~139 of 140 drives and truncated 13.

- DriveProfile gains fw_verify_cdb (the per-drive 0xF1 verify).
- variant_b uploads firmware.len() (now the correct per-drive size from
  the regenerated profiles.json) and issues profile.fw_verify_cdb, falling
  back to the const only for legacy profiles.
- profiles.json regenerated: 79 firmware fields corrected (13 truncated
  recovered to full length, 66 over-reads trimmed).
- Adds a recording-transport regression test asserting the profile's F1
  verify is issued (not the const) and MODE SELECT carries firmware.len().
2026-06-23 15:40:38 -07:00
Matthew Jackson feacfbb10b ld: add public unlock-CDB seam for external consumers
Expose is_unlock_read_buffer() and UNLOCK_MARKER so consumers (bdemu)
can recognise and answer the LibreDrive unlock READ_BUFFER handshake
without open-coding the variant CDB shapes or the verification marker.
These handshake internals must live only in freemkv-unlock-ld.
2026-06-22 17:02:52 -07:00
Matthew Jackson 47d5d1e0b9 Rename to the generic Unlocker contract; add set_max_read_speed
Track libfreemkv's finalized 3-capability Unlocker trait:

  - unlock(...)   -> unlock_drive(...)
  - read_vid(...) -> read_volume_id(...)
  - add set_max_read_speed(...)

set_max_read_speed issues the matched profile's set_speed_max_cdb
(0xBB SET CD SPEED, to max) over the raw transport; a profile without a
set_speed_max_cdb (or no matching profile) is a no-op so the drive stays at
its current speed. read_disc_keys_cdb is left untouched — inert profile data,
never issued, no trait method.

README: document the 3-method contract, the delete-to-comply fallback, and the
#2-only boundary (freemkv uploads RAM microcode to an already-bootloader-flashed
drive; the permanent bootloader flash #1 is the owner's one-time manual step,
never automated). Left a marked MakeMKV attribution placeholder for the
maintainer to author.

Tests: renamed read_vid tests; added set_max_read_speed tests (issues the CDB
when present, no-op when the profile carries none).
2026-06-22 11:05:30 -07:00
Matthew Jackson d6d79fc74c Implement LibreDrive::read_vid OEM Volume ID retrieval
Recover the per-drive READ_BUFFER OEM VID path that lived in libfreemkv
before the unlocker refactor (read_vid_oem in disc/encrypt.rs), now living
inside the unlocker where the per-drive read_vid_cdb template belongs.

read_vid finds the drive's profile; if it carries a read_vid_cdb, issues
it and parses the 36-byte response (signature 00 22 00, VID at [4..20]),
returning Some(vid). No matching profile or no OEM VID CDB returns Ok(None)
so libfreemkv falls back to the cert handshake. VID is thus obtained
without the host certificate + HRL.

Adds response-parse unit tests (well-formed, short response, bad header,
no-profile fallback).
2026-06-22 10:50:54 -07:00
Matthew Jackson 5cec84dfc6 Initial commit: LibreDrive unlocker plugin for libfreemkv
New crate freemkv-unlock-ld — the LibreDrive Unlocker implementation,
split out of libfreemkv so the library stays firmware-clean on crates.io.

Owns everything about HOW MediaTek MT1959 drives are firmware-unlocked:
  - profiles.json (the bundled drive-profile database + DriveProfile parse)
  - src/platform/mt1959/* (variant-A/B firmware upload + unlock handshake)
  - WRITE_BUFFER / MODE SELECT upload, unlock CDBs, disc-speed calibration

Exposes LibreDrive::new() implementing libfreemkv::Unlocker (name/matches/
unlock). Plug it in with one line at process start:

  libfreemkv::register_unlocker(Box::new(freemkv_unlock_ld::LibreDrive::new()));

Depends on libfreemkv (path-patched via gitignored .cargo/config.toml in
dev) for the trait, ScsiTransport, DriveId, and Result. README notes it's
the LibreDrive unlocker (attribution to be added by owner).
2026-06-22 10:32:00 -07:00