19 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 075352158c ld: restore module README with LibreDrive credit to Mike Chen + the MakeMKV team 2026-07-10 14:35:44 -07:00
Matthew Jackson ca8119a167 renesis: detection-only stub; drop unverified vendor command 2026-07-09 16:36:47 -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 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 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 607782ab64 Restructure into freemkv-unlock workspace (ld member + room for more)
libfreemkv owns the Unlocker trait + registry; this repo holds the concrete
unlocker plugins. LibreDrive moves to the ld/ member (package freemkv-unlock-ld
unchanged). Workspace root + repo README describe the general-unlocker model;
adding an unlocker = a new member + one register_unlocker line in the consumer.
libfreemkv dep is the crates.io version; local dev resolves it via the
gitignored .cargo/config.toml patch.
2026-06-22 16:02:57 -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