Commit Graph
62 Commits
Author SHA1 Message Date
Matthew Jackson 4663cdf2bf changelog: 1.6.1 section 2026-08-05 13:59:41 -07:00
Matthew Jackson e7a60d461c v1.6.0: bump version (unified release) 2026-08-03 05:57:16 -07:00
Matthew Jackson e7490240a1 Add CI to the crate everything else depends on
freemkv-unlock had no automated checks at all: no lint, no tests, no
build on any OS but the one a developer happened to be sitting at. It is
the base of the dependency graph — libfreemkv path-deps it, bdemu
path-deps it with the emulation feature, and the rest pull it in
transitively — so a break here takes all seven other repos down, and
until now the first machine to notice would have been the release
workflow.

Full OS matrix, matching libfreemkv's: lint and test on Linux, plus
macOS and Windows building the tests (not `cargo check`, which skips
codegen and never compiles test-only code).

The `emulation` feature gets its own test run. It is off by default and
it is the face bdemu builds against, so nothing was compiling it.

Also adds the leak-guard workflow the other public repos already carry.
2026-07-31 13:46:04 -07:00
Matthew Jackson 3c535ec874 Sweep the pinned toolchain to Rust 1.97
The Windows UI needs current winsafe, whose real minimum is 1.89 (its manifest
under-declares 1.87 while it uses NonNull::from_ref). Rather than stop at the
minimum, this goes to current stable and fixes what that costs.

The counter-intuitive result: 1.97 is CHEAPER than 1.89. libfreemkv had 54
clippy errors at 1.89 and 6 at 1.97, because clippy tightened the noisy
collapsible_if lint in between. Stopping at the minimum would have been the most
expensive choice available.

Roughly 47 lints across the eight repos, the large majority auto-fixed:
libfreemkv 6, freemkv-engine 14, bdemu 8, freemkv-keysources 7, autorip 6,
freemkv-unlock 3, freemkv-i18n 3. The hand-fixed ones are a descending sort to
sort_by_key(Reverse), four manual checked-division sites, a loop counter replaced
by enumerate, and a loop whose first let-else became a while-let.

Worth recording for whoever bumps next: clippy is MSRV-AWARE. Those 54 lints only
appear once the crate DECLARES 1.89 or later, because let-chains become
available. A bare `cargo +1.89 clippy` against a manifest still pinned at 1.87
reports clean and is meaningless — gate with the real precommit script, which is
also the only thing that covers build scripts.

The pin still sits below the Mac default, so it keeps doing its job: catching
lint drift locally before CI sees it.
2026-07-29 21:00:55 -07:00
Matthew Jackson 21cfb17d11 Pin the toolchain to Rust 1.87
The Windows UI needs winsafe, whose current release requires rustc 1.87. The
alternative was pinning winsafe back to an older release, which would bake a
stale API surface into a brand-new UI permanently to dodge one minor version.

The pin's purpose is to sit BELOW the Mac default so clippy drift is caught
locally before CI, not to stay on 1.86 specifically, so 1.87 preserves the
discipline exactly.

Verified before moving anything, not after: `cargo +1.87 clippy -- -D warnings`
and `cargo +1.87 fmt --check` are clean across all eight repos, and the full
precommit gate (fmt + clippy + tests) passes on libfreemkv, autorip, bdemu,
freemkv-engine and freemkv-keysources. Zero new lints, zero formatting drift.
2026-07-29 20:02:02 -07:00
Matthew Jackson 33e65f739c Route conduct reports through GitHub instead of a private address
The enforcement contact was an address on a domain used for internal
infrastructure, which does not belong in a public repository. GitHub needs no
mailbox to exist and is reachable for anyone who can read this file.
2026-07-29 16:07:14 -07:00
Matthew Jackson ac94c80163 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 94cad15a98 changelog: date the released 1.5.x sections (were stale UNRELEASED) 2026-07-28 13:55:23 -07:00
Matthew Jackson 295046054b 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 d339108719 v1.5.1: bump version (unified release) 2026-07-20 17:02:02 -07:00
Matthew Jackson c1447a3dae v1.5.0: bump version (unified release) 2026-07-19 15:37:17 -07:00
Matthew Jackson f073c2c3f9 v1.4.5: bump version (unified release) 2026-07-18 16:07:53 -07:00
Matthew Jackson 9e84eeb284 v1.4.4: bump version (unified release) 2026-07-16 21:43:57 -07:00
Matthew Jackson 5aed39a109 v1.4.3: bump version (unified release) 2026-07-16 21:05:53 -07:00
Matthew Jackson 876943a554 v1.4.2: bump version (unified release) 2026-07-15 19:37:43 -07:00
Matthew Jackson 7c3a46897f v1.4.1: bump version (unified release) 2026-07-14 14:45:21 -07:00
Matthew Jackson b20b38e3d4 Changelog for 1.4.1 2026-07-14 14:43:14 -07:00
Matthew Jackson f2747d5a72 v1.4.0: bump version (unified release) 2026-07-13 19:28:44 -07:00
Matthew Jackson 2877ccd022 Changelog: 1.4.0 (Blu-ray 3D / MVC) 2026-07-13 12:47:03 -07:00
Matthew Jackson af49583865 ld: restore module README with LibreDrive credit to Mike Chen + the MakeMKV team 2026-07-10 14:35:44 -07:00
Matthew Jackson ef432b43ca 1.3.2: version sync
Inherits libfreemkv 1.3.2.
2026-07-10 14:00:35 -07:00
Matthew Jackson fe23557555 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 336e44f7a9 docs: add 1.3.0 changelog 2026-07-10 08:19:22 -07:00
Matthew Jackson e9cd114489 renesis: detection-only stub; drop unverified vendor command 2026-07-09 16:36:47 -07:00
Matthew Jackson 2d7c6b3cec release: bump to 1.3.0 2026-07-08 14:48:50 -07:00
Matthew Jackson 6279fa7fec 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 67919326f1 v1.2.2: bump version (unified release) 2026-07-03 20:50:05 -07:00
Matthew Jackson d39b9b8a82 v1.2.1: bump version 2026-07-01 17:30:25 -07:00
Matthew Jackson 9dbf3f6381 unlock: drop applies_to — the matrix reports did-work, not matched 2026-07-01 10:44:39 -07:00
Matthew Jackson d3255cafc4 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 558bcecf8a Revert "unlock: drive-features capability (stock riplock lift for any disc)"
This reverts commit 07e030e4ea.
2026-07-01 09:34:58 -07:00
Matthew Jackson 07e030e4ea 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 f47200518b README: rewrite for the single-crate, base-of-graph architecture 2026-06-29 21:15:04 -07:00
Matthew Jackson b91ccfa4f8 Cargo: publish = false (firmware crate — git-tag-only, never crates.io) 2026-06-29 21:04:05 -07:00
Matthew Jackson 817c7285f4 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 19f2b959a2 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 26512f4895 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 6c75592387 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 a244589eca 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 7ec8f0316e 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 06a259f60e 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 ed54b2bb01 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 ca5c8275fe 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 7d3ce8ef86 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 e377a0067a 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 2a9a15602a 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 3fdbf01a16 v1.1.0: bump version (unified release) 2026-06-28 18:20:14 -07:00
Matthew Jackson 493aceaf71 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 1eea38e9df v1.0.0-rc.5.3: bump version (unified release) 2026-06-24 21:55:19 -07:00
Matthew Jackson 967b011c96 v1.0.0-rc.5.2: bump version (unified release) 2026-06-24 17:19:59 -07:00
Matthew Jackson f2a3b369c7 v1.0.0-rc.5.1: bump ld version + libfreemkv dep (exact) 2026-06-24 14:47:16 -07:00
Matthew Jackson 2465ff6f93 Add Contributor Covenant v2.1 Code of Conduct 2026-06-24 10:45:07 -07:00
Matthew Jackson 3ea5c3969d v1.0.0-rc.5: bump ld version + libfreemkv dep (exact) 2026-06-24 10:41:57 -07:00
Matthew Jackson 0e7e5bb509 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 6c55f3250c 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 78304ab877 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 1b96e33b0b 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 77068ac9ee docs: generic repo README; ld README keeps specifics + MakeMKV/Mike Chen credit 2026-06-22 16:53:14 -07:00
Matthew Jackson 32c14a092d 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