diff --git a/src/aacs/mod.rs b/src/aacs/mod.rs index 17f8c88..0c6d0ce 100644 --- a/src/aacs/mod.rs +++ b/src/aacs/mod.rs @@ -44,6 +44,10 @@ impl Default for AacsCert { } impl Unlocker for AacsCert { + fn name(&self) -> &'static str { + "AACS" + } + fn matches(&self, ctx: &UnlockCtx) -> bool { ctx.kind == DiscKind::Aacs } diff --git a/src/css/mod.rs b/src/css/mod.rs index efcd387..04af624 100644 --- a/src/css/mod.rs +++ b/src/css/mod.rs @@ -160,6 +160,10 @@ impl Default for CssUnlocker { } impl crate::Unlocker for CssUnlocker { + fn name(&self) -> &'static str { + "CSS" + } + fn matches(&self, ctx: &crate::UnlockCtx) -> bool { ctx.kind == crate::DiscKind::Css } diff --git a/src/ld/mod.rs b/src/ld/mod.rs index 9541595..1fa3bcd 100644 --- a/src/ld/mod.rs +++ b/src/ld/mod.rs @@ -121,10 +121,22 @@ impl Unlocker for LibreDrive { /// probed (`kind == Unknown`) and keys off the drive identity. It must NOT /// fire during the later content-keyed dispatch (Aacs/Css), or a DVD/Blu-ray /// in a profiled drive would be re-firmware-unlocked. + fn name(&self) -> &'static str { + "LibreDrive" + } + fn matches(&self, ctx: &UnlockCtx) -> bool { ctx.kind == crate::DiscKind::Unknown && profile::find_bundled(ctx.drive_id).is_some() } + /// For the report, the drive unlocker applies whenever it recognises the + /// DRIVE — independent of disc kind (its live `matches` is gated to the + /// Unknown-kind init phase, which would falsely read "no" once the disc kind + /// is known). + fn applies_to(&self, ctx: &UnlockCtx) -> bool { + profile::find_bundled(ctx.drive_id).is_some() + } + /// Firmware-unlock the drive and report its OEM Volume ID. The unlocked drive /// serves CLEAR content, so `drive_unlocked: true` and there is no bus key. /// diff --git a/src/ld/profile.rs b/src/ld/profile.rs index 7b06895..d1df7e8 100644 --- a/src/ld/profile.rs +++ b/src/ld/profile.rs @@ -290,10 +290,7 @@ fn load_from_str(data: &str) -> Result { /// profile; the looser pass lets a drive whose firmware date we don't have /// on file still match a same-model profile. All comparisons are /// whitespace-trimmed. Returns the first section that yields a match. -pub fn find_by_drive_id( - profiles: &Profiles, - drive_id: &crate::DriveId, -) -> Option { +pub fn find_by_drive_id(profiles: &Profiles, drive_id: &crate::DriveId) -> Option { let v = drive_id.vendor_id.trim(); let r = drive_id.product_revision.trim(); let vs = drive_id.vendor_specific.trim(); diff --git a/src/lib.rs b/src/lib.rs index c3a249c..0d8dfd5 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -110,8 +110,26 @@ pub enum UnlockError { /// NOTE: drive tuning (e.g. SET CD SPEED to lift riplock) is deliberately NOT /// here — that is the consumer's concern, not bus removal. pub trait Unlocker: Send + Sync { - /// True if this unlocker applies to the given context (drive id + disc kind). + /// Short, stable identifier for this unlocker (e.g. "LibreDrive", "AACS", + /// "CSS"). The ONE place a name lives — apps render the unlocker report from + /// [`all_unlockers`], never hardcoding names, so adding/removing an unlocker + /// updates every report with no app change. + fn name(&self) -> &'static str; + + /// True if this unlocker applies to the given context (drive id + disc kind) + /// during live dispatch — the gate [`crate::all_unlockers`] uses to decide + /// whether to RUN it. fn matches(&self, ctx: &UnlockCtx) -> bool; + + /// Report-only: would this unlocker apply to this drive + disc, for a + /// user-facing unlocker matrix? Distinct from [`matches`](Unlocker::matches), + /// which is phase-gated (e.g. the drive-prep unlocker only `matches` at the + /// Unknown-kind init phase). Default: same as `matches`; the drive-keyed + /// unlocker overrides it to report on drive identity regardless of disc kind. + fn applies_to(&self, ctx: &UnlockCtx) -> bool { + self.matches(ctx) + } + /// Remove the bus-encryption barrier, returning what was learned. fn unlock( &self,