Commit Graph
78 Commits
Author SHA1 Message Date
Matthew Jackson 50205a0286 ci: stop Dependabot proposing Rust versions that do not exist
`dtolnay/rust-toolchain` is not a dependency in the sense Dependabot
means: it is versioned by the Rust release it installs, and the tag we
pin is the toolchain CI is pinned to on purpose — precommit.sh runs the
same one locally, so a lint that passes on a developer's newer default
cannot pass CI by accident.

Read as semver, those tags produced a 1.97.0 -> 1.100.0 proposal, a Rust
version that does not exist. Every such PR 404s on toolchain download
across all eight repos and regenerates weekly: eight permanently-red PRs
that promote.yml then has to special-case when it decides whether dev is
green.

freemkv already carries this ignore; this is the same block in the other
seven, so the fleet stays uniform. Bumping the toolchain stays a
deliberate, all-eight-repos change made by hand together with
precommit.sh.
2026-08-18 12:14:58 -07:00
Matthew Jackson 274e9c66f5 v1.6.4: bump version (unified release) 2026-08-15 09:13:15 -07:00
Matthew Jackson d05b9bce18 Version sync to 1.6.4 2026-08-15 08:14:31 -07:00
Matthew Jackson f9df7e0fb3 Carry the same licence and community files as the other repos
These are eight public repos that ship one product on one version, and
they had drifted: freemkv-engine carried no LICENSE at all (GitHub
reported its licence as none), two crates had no code of conduct, four
had no contributing guide, and none had a security policy — so there was
no private route to report a vulnerability in a disc-decryption tool.

SECURITY.md names GitHub Security Advisories on each repo rather than an
address, so there is nothing to keep in sync and no inbox to go stale.
2026-08-09 22:08:22 -07:00
Matthew Jackson 9a4f29b04c Keep the cross-platform jobs for the release candidate
dev is where work lands and is meant to be pushed to often, so what runs
there should be the cheap answer to "did I break it": lint, tests and the
Linux build. The macOS and Windows jobs now run on qa and main instead of
on every push to dev.

Nothing is deleted and no platform goes unchecked before a release. qa.yml
already covers macOS and Windows independently, and the jobs that live only
here -- the Intel macOS build, the Windows release build -- still run, on
the branches where a cross-platform break is worth blocking on.

They are SKIPPED on dev via `if`, not left unscheduled. A queued job would
be worse than a slow one: release.sh's CI gate refuses while any run for
the commit is still in progress, so a job that never gets a runner blocks
releases silently, with no error anywhere. That is the failure the
real-media note in qa.yml describes, and it is why this is an `if` on the
job rather than a narrower set of trigger branches.
2026-08-09 20:24:05 -07:00
Matthew Jackson 6a8253f889 Release 1.6.3
Version bump and changelog for the 1.6.3 sync. All eight crates ship the
same version, so the crates with no functional change this cycle say so
rather than carrying an empty section.
2026-08-09 20:01:33 -07:00
Matthew Jackson 2684f5f28e base64: match the workspace on 0.23
Every other crate declares 0.23; this one alone said 0.23.1. Cargo
unifies them, so this is uniformity rather than a fix -- but a lone
pinned patch version is the kind of thing that quietly becomes a real
split at the next major.
2026-08-09 18:43:09 -07:00
Matthew Jackson f0d936f4b6 Move to rand 0.10 and aes 0.9
Both are API renames rather than behaviour changes, but this is the AACS
and CSS crypto path, so it is verified by output rather than by the fact
that it compiles.

rand: `RngCore` no longer sits at the crate root -- `fill_bytes` moved to
the re-exported `Rng` trait -- and `thread_rng()` is now `rng()`.

aes: the cipher traits gained their `BlockCipher` prefix and
`generic_array::GenericArray` became `Array`. The obvious translation
uses `Array::from_slice`, which is deprecated in the new version and
would fail clippy's `-D warnings`; these use the `From<[T; N]>`
conversion the crate points at instead.

The proof this is byte-exact is `test_aes_cmac_nist_kat_full_block`,
which checks the exact MAC from NIST SP 800-38B Appendix D.1 -- a wrong
byte order or a mis-sized array cannot produce that value. All 95 tests
pass in both debug and release.
2026-08-09 17:50:01 -07:00
Matthew Jackson 53d9e15d3d Have Dependabot watch the dependencies, and land its PRs on dev
Version updates were never configured here -- only security alerts, which
report but never open a pull request. So the absence of Dependabot PRs was
not "nothing to update", it was "nothing was checking".

Updates target `dev`, never `main`. main is a release pointer that
release.sh moves to each tag, so a bot commit on it would put work there
that no tag contains -- the exact state that aborted the 1.6.2 cascade at
its last step, and pointing a scheduled bot at main would recreate it
weekly.

Minor and patch bumps are grouped into one PR rather than one per crate:
eight repos times a handful of dependencies is a volume nobody reads, and
an unread PR queue is indistinguishable from no updates at all.

The freemkv crates are excluded. They depend on each other by git tag,
re-pinned by release.sh inside the release commit, and Dependabot cannot
see that cascade -- a PR bumping one could pin a version whose tag does
not exist yet.
2026-08-09 16:48:29 -07:00
Matthew Jackson 65e02f4b79 v1.6.2: bump version (unified release) 2026-08-08 22:18:13 -07:00
Matthew Jackson 10126cdbf6 changelog: 1.6.2 2026-08-08 08:53:11 -07:00
Matthew Jackson 887d154014 v1.6.1: bump version (unified release) 2026-08-07 19:08:30 -07:00
Matthew Jackson e9ed2ffa85 changelog: mark 1.6.1 UNRELEASED so release.sh can date it
release.sh dates the heading when it cuts the tag, and refuses a bare
'## [1.6.1]' because it cannot tell an undated heading from one it has
already stamped. Shipping a tag whose public changelog says UNRELEASED is
permanent and cannot be corrected inside that tag, so the check is right
to stop rather than guess.
2026-08-07 17:57:22 -07:00
Matthew Jackson 3a5f92da91 Scope the qa matrix to the platforms each crate actually supports
The first cut ran release-profile tests on all three platforms everywhere,
which invented coverage no crate had ever claimed. bdemu is Linux-only — its
ci.yml has no macOS or Windows job at all, and its tests call
ExitStatus::signal(), which does not exist on Windows, so they do not compile
there let alone run. The rest follow the policy ci.yml already set: tests
execute on Linux and macOS, Windows compiles them so the cfg(windows) halves
still get codegen before release time.

That policy is now explicit in qa.yml as a windows-build job rather than
implied by which jobs happen to exist.

Recorded, not papered over: running the suites on Windows DOES find real
defects — autorip has five Windows path-handling failures in mover.rs and
freemkv-engine has three tests hardcoding /dev/null. freemkv ships a Windows
GUI, so they matter. They are pre-existing rather than 1.6.1 regressions, and
fixing them is its own piece of work.

Also build the binary before cli-parity.sh. cli-integration.sh builds its own;
cli-parity.sh only checks for one and exits 2 with "build it first", so both
legs died before running a single case.
2026-08-05 21:22:30 -07:00
Matthew Jackson 58dc46037e Stamp every qa push as a release candidate
Every push to qa now tags v<version>-rc<N>, N incrementing, before the gates
run. That answers "which build is on qa, and is it the one I tested?" without
anyone having to remember it.

The tag lands whether the run goes green or red, deliberately. A red candidate
needs a name more than a green one does: "rc3 failed release-tests on windows"
is a sentence you can act on, "qa is red" is not. Red on qa is the gate doing
its job — the branch saying this is not production worth yet.

release.yml now excludes v*-rc*. Its trigger was v*, which matches the
candidate tags, so without this every push to qa would have built and PUBLISHED
a GitHub release — including for the candidates that failed.
2026-08-05 21:03:01 -07:00
Matthew Jackson 0ed613d7a9 Add the qa gate: dev for speed, qa for proof
dev -> qa -> main, across every public repo.

dev is where work lands and is meant to be pushed to often, so ci.yml stays
the fast answer: fmt, clippy, unit tests, leak-guard. qa is the release
candidate, and qa.yml is the claim that a commit is production worth —
release-profile tests on all three platforms, and the Linux cross-target
clippy that the local precommit gate has always run but CI never did.

Release profile matters as its own gate: overflow checks are off, debug_assert
is compiled out, and inlining changes what the optimiser can prove. A test
that only passes in debug never guarded the binary anyone ships.

qa.yml checks siblings out at qa rather than dev. This is not a monorepo, and
a qa run resolving its dependencies from unreleased dev tips would go green on
a combination that is not the one shipping — the exact mismatch the branch
model exists to prevent. ci.yml now tracks whichever branch triggered it for
the same reason, since it also fires on qa.

The consequence is that repos move to qa together, in dependency order. qa is
backfilled from main so an unchanged crate still presents working code to the
crates built on it.
2026-08-05 20:59:34 -07:00
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