unlock: drop Feature::Bd, rename Uhd→Unrestricted (mirror fw 0.9.2 ABI)

This commit is contained in:
Matthew Jackson
2026-09-24 20:40:56 -07:00
parent eef8e958fc
commit 89861e6f40
2 changed files with 59 additions and 72 deletions
+37 -50
View File
@@ -210,18 +210,16 @@ pub enum Feature {
/// [`REGION_BD_A`]/`_B`/`_C` (`0x0A`/`0x0B`/`0x0C`) = force BD region A/B/C; /// [`REGION_BD_A`]/`_B`/`_C` (`0x0A`/`0x0B`/`0x0C`) = force BD region A/B/C;
/// [`REGION_FREE`] (`0x0F`) = region-free (any disc plays). /// [`REGION_FREE`] (`0x0F`) = region-free (any disc plays).
Region = 0x02, Region = 0x02,
/// UHD (AACS 2.0) capability gate. [`STATE_PASSTHROUGH`] (`0xFF`) = OEM (as /// Unrestricted (BD/UHD) capability widen gate. fw 0.9.2 unified the former
/// shipped); [`STATE_OFF`] (`0x00`) = No (refuse UHD discs); [`STATE_ON`] /// separate BD (AACS 1.0) and UHD (AACS 2.0) accept gates into this single
/// (`0x01`) = Yes (accept UHD — the mode gate is neutralized so the drive /// lever. [`STATE_PASSTHROUGH`] (`0xFF`) = OEM (as shipped); [`STATE_OFF`]
/// engages UHD discs). On the byte-extraction classifier (e.g. BU40N) the No /// (`0x00`) = No (refuse BD/UHD discs); [`STATE_ON`] (`0x01`) = Yes (accept
/// (OFF) leg currently reads back but behaves == OEM (boot-safe no-op, pending /// BD/UHD — the mode gate is neutralized so the drive engages both). On the
/// deeper RE) — do NOT treat OFF as a proven UHD disable there. /// byte-extraction classifier (e.g. BU40N) the No (OFF) leg currently reads
Uhd = 0x03, /// back but behaves == OEM (boot-safe no-op, pending deeper RE) — do NOT
/// Blu-ray (AACS 1.0) capability gate. [`STATE_PASSTHROUGH`] (`0xFF`) = OEM (BD /// treat OFF as a proven disable there. Wire id `0x04` (was `Bd`) is retired:
/// engaged as shipped); [`STATE_OFF`] (`0x00`) = No (force-refuse BD discs — the /// the BD slot is now unused/reserved, the firmware treats it as a no-op.
/// drive raises its own `6F` refusal sense); [`STATE_ON`] (`0x01`) = Yes (accept Unrestricted = 0x03,
/// BD — an enable direction, not a no-op).
Bd = 0x04,
/// Host Revocation List handling on the cert path. [`STATE_PASSTHROUGH`] /// Host Revocation List handling on the cert path. [`STATE_PASSTHROUGH`]
/// (`0xFF`) = OEM enforce; [`STATE_OFF`] (`0x00`) = off (skip the HRL lookup — /// (`0xFF`) = OEM enforce; [`STATE_OFF`] (`0x00`) = off (skip the HRL lookup —
/// revoked certs accepted, non-destructive, the unlock direction); [`STATE_ON`] /// revoked certs accepted, non-destructive, the unlock direction); [`STATE_ON`]
@@ -239,11 +237,10 @@ pub enum Feature {
/// The set of all features, in the canonical order the IDENTITY feature-state /// The set of all features, in the canonical order the IDENTITY feature-state
/// table serialises them (and the order [`FirmwareControl::states`] probes). /// table serialises them (and the order [`FirmwareControl::states`] probes).
pub const ALL_FEATURES: [Feature; 6] = [ pub const ALL_FEATURES: [Feature; 5] = [
Feature::Speed, Feature::Speed,
Feature::Region, Feature::Region,
Feature::Uhd, Feature::Unrestricted,
Feature::Bd,
Feature::Hrl, Feature::Hrl,
Feature::Encryption, Feature::Encryption,
]; ];
@@ -446,10 +443,8 @@ pub struct FeatureStates {
pub speed: u8, pub speed: u8,
/// [`Feature::Region`] state. /// [`Feature::Region`] state.
pub region: u8, pub region: u8,
/// [`Feature::Uhd`] state. /// [`Feature::Unrestricted`] state.
pub uhd: u8, pub unrestricted: u8,
/// [`Feature::Bd`] state.
pub bd: u8,
/// [`Feature::Hrl`] state. /// [`Feature::Hrl`] state.
pub hrl: u8, pub hrl: u8,
/// [`Feature::Encryption`] state (the consolidated cert/bus bypass). /// [`Feature::Encryption`] state (the consolidated cert/bus bypass).
@@ -462,8 +457,7 @@ impl FeatureStates {
FeatureStates { FeatureStates {
speed: STATE_PASSTHROUGH, speed: STATE_PASSTHROUGH,
region: STATE_PASSTHROUGH, region: STATE_PASSTHROUGH,
uhd: STATE_PASSTHROUGH, unrestricted: STATE_PASSTHROUGH,
bd: STATE_PASSTHROUGH,
hrl: STATE_PASSTHROUGH, hrl: STATE_PASSTHROUGH,
encryption: STATE_PASSTHROUGH, encryption: STATE_PASSTHROUGH,
} }
@@ -474,8 +468,7 @@ impl FeatureStates {
match feature { match feature {
Feature::Speed => self.speed, Feature::Speed => self.speed,
Feature::Region => self.region, Feature::Region => self.region,
Feature::Uhd => self.uhd, Feature::Unrestricted => self.unrestricted,
Feature::Bd => self.bd,
Feature::Hrl => self.hrl, Feature::Hrl => self.hrl,
Feature::Encryption => self.encryption, Feature::Encryption => self.encryption,
} }
@@ -485,8 +478,7 @@ impl FeatureStates {
match feature { match feature {
Feature::Speed => self.speed = state, Feature::Speed => self.speed = state,
Feature::Region => self.region = state, Feature::Region => self.region = state,
Feature::Uhd => self.uhd = state, Feature::Unrestricted => self.unrestricted = state,
Feature::Bd => self.bd = state,
Feature::Hrl => self.hrl = state, Feature::Hrl => self.hrl = state,
Feature::Encryption => self.encryption = state, Feature::Encryption => self.encryption = state,
} }
@@ -533,6 +525,10 @@ pub enum ArmRecipe {
BypassBd, BypassBd,
/// [`FirmwareControl::arm_bypass_uhd`]. /// [`FirmwareControl::arm_bypass_uhd`].
BypassUhd, BypassUhd,
// NOTE: `Feature::Unrestricted` now unifies the former BD+UHD accept gates
// (fw 0.9.2); the OemBd/OemUhd/BypassBd/BypassUhd recipe names above are
// kept as-is (they name the disc TYPE being ripped, not the retired
// per-format feature), see `arm_oem_uhd`/`arm_bypass_uhd` below.
/// [`FirmwareControl::arm_stealth_oem`]. /// [`FirmwareControl::arm_stealth_oem`].
StealthOem, StealthOem,
} }
@@ -697,24 +693,15 @@ impl<'a> FirmwareControl<'a> {
// ── Typed setters ──────────────────────────────────────────────────────── // ── Typed setters ────────────────────────────────────────────────────────
/// Force the UHD (AACS 2.0) capability gate on (drive engages UHD discs). /// Force the Unrestricted (BD/UHD) capability gate on (drive engages both
pub fn enable_uhd(&mut self) -> Result<()> { /// BD and UHD discs). fw 0.9.2 unified the former separate UHD/BD gates
self.set(Feature::Uhd, STATE_ON) /// into this single lever.
pub fn enable_unrestricted(&mut self) -> Result<()> {
self.set(Feature::Unrestricted, STATE_ON)
} }
/// Force the UHD capability gate off. /// Force the Unrestricted (BD/UHD) capability gate off.
pub fn disable_uhd(&mut self) -> Result<()> { pub fn disable_unrestricted(&mut self) -> Result<()> {
self.set(Feature::Uhd, STATE_OFF) self.set(Feature::Unrestricted, STATE_OFF)
}
/// Force the Blu-ray (AACS 1.0) capability gate on.
pub fn enable_bd(&mut self) -> Result<()> {
self.set(Feature::Bd, STATE_ON)
}
/// Force the Blu-ray capability gate off (drive refuses BD discs). Sends the
/// uniform [`STATE_OFF`] (`0x00`) — an unarmed image boots to [`STATE_PASSTHROUGH`]
/// (`0xFF`, OEM) via the firmware power-on hook, so `0x00` no longer collides
/// with the boot value (the retired `0x02` sentinel is gone).
pub fn disable_bd(&mut self) -> Result<()> {
self.set(Feature::Bd, STATE_OFF)
} }
/// Skip the HRL lookup (revoked certs accepted; non-destructive). The unlock /// Skip the HRL lookup (revoked certs accepted; non-destructive). The unlock
/// direction is now [`STATE_OFF`] (`0x00` = HRL enforcement off). /// direction is now [`STATE_OFF`] (`0x00` = HRL enforcement off).
@@ -766,12 +753,12 @@ impl<'a> FirmwareControl<'a> {
/// **arm_oem_uhd** — OEM-style UHD (AACS 2.0) rip. /// **arm_oem_uhd** — OEM-style UHD (AACS 2.0) rip.
/// ///
/// Sets: `Uhd = on` (mode-gate neutralized so the drive engages the UHD /// Sets: `Unrestricted = on` (mode-gate neutralized so the drive engages
/// disc), `Hrl = skip` (revocation off), `Encryption = off` (content /// the UHD disc), `Hrl = skip` (revocation off), `Encryption = off`
/// de-bussed — the consolidated cert/bus bypass). Rips: UHD via the OEM /// (content de-bussed — the consolidated cert/bus bypass). Rips: UHD via
/// path. /// the OEM path.
pub fn arm_oem_uhd(&mut self) -> Result<()> { pub fn arm_oem_uhd(&mut self) -> Result<()> {
self.set_verify(Feature::Uhd, STATE_ON)?; self.set_verify(Feature::Unrestricted, STATE_ON)?;
self.set_verify(Feature::Hrl, STATE_OFF)?; self.set_verify(Feature::Hrl, STATE_OFF)?;
self.set_verify(Feature::Encryption, STATE_OFF) self.set_verify(Feature::Encryption, STATE_OFF)
} }
@@ -788,11 +775,11 @@ impl<'a> FirmwareControl<'a> {
/// **arm_bypass_uhd** — full-bypass UHD rip (NO host cert needed). /// **arm_bypass_uhd** — full-bypass UHD rip (NO host cert needed).
/// ///
/// Sets: `Uhd = on` (engage the UHD disc), `Encryption = off` (the /// Sets: `Unrestricted = on` (engage the UHD disc), `Encryption = off`
/// consolidated cert/bus bypass), and `Hrl = skip` (revocation off). /// (the consolidated cert/bus bypass), and `Hrl = skip` (revocation off).
/// Rips: UHD with no cert. /// Rips: UHD with no cert.
pub fn arm_bypass_uhd(&mut self) -> Result<()> { pub fn arm_bypass_uhd(&mut self) -> Result<()> {
self.set_verify(Feature::Uhd, STATE_ON)?; self.set_verify(Feature::Unrestricted, STATE_ON)?;
self.set_verify(Feature::Hrl, STATE_OFF)?; self.set_verify(Feature::Hrl, STATE_OFF)?;
self.set_verify(Feature::Encryption, STATE_OFF) self.set_verify(Feature::Encryption, STATE_OFF)
} }
+22 -22
View File
@@ -24,12 +24,13 @@ fn verb_values_match_abi() {
fn feature_values_match_abi() { fn feature_values_match_abi() {
assert_eq!(Feature::Speed as u8, 0x01); assert_eq!(Feature::Speed as u8, 0x01);
assert_eq!(Feature::Region as u8, 0x02); assert_eq!(Feature::Region as u8, 0x02);
assert_eq!(Feature::Uhd as u8, 0x03); assert_eq!(Feature::Unrestricted as u8, 0x03);
assert_eq!(Feature::Bd as u8, 0x04);
assert_eq!(Feature::Hrl as u8, 0x05); assert_eq!(Feature::Hrl as u8, 0x05);
assert_eq!(Feature::Encryption as u8, 0x06); assert_eq!(Feature::Encryption as u8, 0x06);
// Wire id 0x04 (was `Bd`) is retired — fw 0.9.2 unified BD+UHD into
// Unrestricted; the BD slot is now unused/reserved (fw no-ops it).
// Wire id 0x07 (was `Bus`) is retired — proven inert on BU40N/MT1959. // Wire id 0x07 (was `Bus`) is retired — proven inert on BU40N/MT1959.
assert_eq!(ALL_FEATURES.len(), 6); assert_eq!(ALL_FEATURES.len(), 5);
} }
#[test] #[test]
@@ -425,10 +426,11 @@ fn states_probes_every_feature_in_order() {
} }
assert_eq!(states.speed, SPEED_MAX); assert_eq!(states.speed, SPEED_MAX);
assert_eq!(states.hrl, STATE_ON); assert_eq!(states.hrl, STATE_ON);
// Six GETs, one per feature, in ALL_FEATURES order (Bus id 0x07 retired). // Five GETs, one per feature, in ALL_FEATURES order (Bd id 0x04 and Bus id
assert_eq!(m.cdbs.len(), 6); // 0x07 both retired).
assert_eq!(m.cdbs.len(), 5);
let features: Vec<u8> = m.cdbs.iter().map(|c| c[CDB_FEATURE]).collect(); let features: Vec<u8> = m.cdbs.iter().map(|c| c[CDB_FEATURE]).collect();
assert_eq!(features, vec![0x01, 0x02, 0x03, 0x04, 0x05, 0x06]); assert_eq!(features, vec![0x01, 0x02, 0x03, 0x05, 0x06]);
assert!(m.verbs().iter().all(|&v| v == Verb::Get as u8)); assert!(m.verbs().iter().all(|&v| v == Verb::Get as u8));
} }
@@ -452,8 +454,7 @@ fn typed_setters_issue_expected_set_cdbs() {
let mut m = FwMock::new(); let mut m = FwMock::new();
{ {
let mut fw = FirmwareControl::new(&mut m); let mut fw = FirmwareControl::new(&mut m);
fw.enable_uhd().unwrap(); fw.enable_unrestricted().unwrap();
fw.disable_bd().unwrap();
fw.skip_hrl().unwrap(); fw.skip_hrl().unwrap();
fw.disable_encryption().unwrap(); fw.disable_encryption().unwrap();
fw.region_free().unwrap(); fw.region_free().unwrap();
@@ -461,17 +462,16 @@ fn typed_setters_issue_expected_set_cdbs() {
fw.force_region_dvd(2).unwrap(); fw.force_region_dvd(2).unwrap();
fw.unlock_speed().unwrap(); fw.unlock_speed().unwrap();
} }
assert_eq!(m.cdbs[0], build_set_cdb(Feature::Uhd, STATE_ON)); assert_eq!(m.cdbs[0], build_set_cdb(Feature::Unrestricted, STATE_ON));
assert_eq!(m.cdbs[1], build_set_cdb(Feature::Bd, STATE_OFF));
// The HRL/Encryption unlock direction is STATE_OFF (0x00), not STATE_ON. // The HRL/Encryption unlock direction is STATE_OFF (0x00), not STATE_ON.
assert_eq!(m.cdbs[2], build_set_cdb(Feature::Hrl, STATE_OFF)); assert_eq!(m.cdbs[1], build_set_cdb(Feature::Hrl, STATE_OFF));
assert_eq!(m.cdbs[3], build_set_cdb(Feature::Encryption, STATE_OFF)); assert_eq!(m.cdbs[2], build_set_cdb(Feature::Encryption, STATE_OFF));
// Region-free is REGION_FREE (0x0F) — 0x01 now forces DVD region 1. // Region-free is REGION_FREE (0x0F) — 0x01 now forces DVD region 1.
assert_eq!(m.cdbs[4], build_set_cdb(Feature::Region, REGION_FREE)); assert_eq!(m.cdbs[3], build_set_cdb(Feature::Region, REGION_FREE));
assert_eq!(m.cdbs[5], build_set_cdb(Feature::Region, REGION_BD_B)); assert_eq!(m.cdbs[4], build_set_cdb(Feature::Region, REGION_BD_B));
// force_region_dvd(2) = REGION_DVD_BASE + 2 = 0x02 under the new base. // force_region_dvd(2) = REGION_DVD_BASE + 2 = 0x02 under the new base.
assert_eq!(m.cdbs[6], build_set_cdb(Feature::Region, 0x02)); assert_eq!(m.cdbs[5], build_set_cdb(Feature::Region, 0x02));
assert_eq!(m.cdbs[7], build_set_cdb(Feature::Speed, SPEED_MAX)); assert_eq!(m.cdbs[6], build_set_cdb(Feature::Speed, SPEED_MAX));
} }
#[test] #[test]
@@ -511,10 +511,10 @@ fn arm_oem_uhd_sets_uhd_hrl_encryption_each_verified() {
vec![0x02, 0x03, 0x02, 0x03, 0x02, 0x03], vec![0x02, 0x03, 0x02, 0x03, 0x02, 0x03],
"each set is verified by a get" "each set is verified by a get"
); );
assert_eq!(m.cdbs[0], build_set_cdb(Feature::Uhd, STATE_ON)); assert_eq!(m.cdbs[0], build_set_cdb(Feature::Unrestricted, STATE_ON));
assert_eq!(m.cdbs[2], build_set_cdb(Feature::Hrl, STATE_OFF)); assert_eq!(m.cdbs[2], build_set_cdb(Feature::Hrl, STATE_OFF));
assert_eq!(m.cdbs[4], build_set_cdb(Feature::Encryption, STATE_OFF)); assert_eq!(m.cdbs[4], build_set_cdb(Feature::Encryption, STATE_OFF));
assert_eq!(m.states.uhd, STATE_ON); assert_eq!(m.states.unrestricted, STATE_ON);
assert_eq!(m.states.hrl, STATE_OFF); assert_eq!(m.states.hrl, STATE_OFF);
assert_eq!(m.states.encryption, STATE_OFF); assert_eq!(m.states.encryption, STATE_OFF);
} }
@@ -542,11 +542,11 @@ fn arm_bypass_uhd_sets_uhd_hrl_encryption() {
let mut fw = FirmwareControl::new(&mut m); let mut fw = FirmwareControl::new(&mut m);
fw.arm_bypass_uhd().expect("armed"); fw.arm_bypass_uhd().expect("armed");
} }
// Recipe order: Uhd=on, Hrl=off, Encryption=off (each SET then verifying GET). // Recipe order: Unrestricted=on, Hrl=off, Encryption=off (each SET then verifying GET).
assert_eq!(m.cdbs[0], build_set_cdb(Feature::Uhd, STATE_ON)); assert_eq!(m.cdbs[0], build_set_cdb(Feature::Unrestricted, STATE_ON));
assert_eq!(m.cdbs[2], build_set_cdb(Feature::Hrl, STATE_OFF)); assert_eq!(m.cdbs[2], build_set_cdb(Feature::Hrl, STATE_OFF));
assert_eq!(m.cdbs[4], build_set_cdb(Feature::Encryption, STATE_OFF)); assert_eq!(m.cdbs[4], build_set_cdb(Feature::Encryption, STATE_OFF));
assert_eq!(m.states.uhd, STATE_ON); assert_eq!(m.states.unrestricted, STATE_ON);
assert_eq!(m.states.hrl, STATE_OFF); assert_eq!(m.states.hrl, STATE_OFF);
assert_eq!(m.states.encryption, STATE_OFF); assert_eq!(m.states.encryption, STATE_OFF);
} }
@@ -555,7 +555,7 @@ fn arm_bypass_uhd_sets_uhd_hrl_encryption() {
fn arm_stealth_oem_resets_then_verifies_all_passthrough() { fn arm_stealth_oem_resets_then_verifies_all_passthrough() {
let mut m = FwMock::new(); let mut m = FwMock::new();
m.states.encryption = STATE_ON; m.states.encryption = STATE_ON;
m.states.uhd = STATE_ON; m.states.unrestricted = STATE_ON;
{ {
let mut fw = FirmwareControl::new(&mut m); let mut fw = FirmwareControl::new(&mut m);
fw.arm_stealth_oem().expect("disarmed"); fw.arm_stealth_oem().expect("disarmed");