From 800c78f86bafed2b851e3e9729edb762bd84dfed Mon Sep 17 00:00:00 2001 From: Matthew Jackson <1085847+MattJackson@users.noreply.github.com> Date: Sun, 4 Oct 2026 18:42:19 -0700 Subject: [PATCH] renesas: vendor CDBs from pioneer-optical The identity read (3C 02 F1), the two gated memory reads (3C 02 B0 at 0x04 and 0x500000, 0xA4 bytes) and the read-unlock knock (3B 02 41 A5 AA AA) come from the crate's constructors; the bytes on the wire are unchanged and the knock test still pins them. The crate's flash::read_memory is not used: it knocks before every read and stops on a refused knock, where this sequence reads first and tries the second window even when the knock is refused. --- CHANGELOG.md | 1 + Cargo.lock | 7 +++++++ Cargo.toml | 2 ++ src/renesas/mod.rs | 33 +++++++++++++++++++-------------- 4 files changed, 29 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a297ca4..bc4048e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ ### Changed +- The Renesas/Pioneer unlocker builds its vendor CDBs (identity read, gated memory reads, read-unlock knock) with the `pioneer-optical` crate instead of hard-coded bytes; the commands sent are unchanged. - AACS handshake diagnostics that a scan now reports itself log below WARN: unsupported or unverifiable drive certs, VID read failures and transport faults at DEBUG, `read_data_key_dropped` at INFO. ## [1.7.7] — 2026-09-26 diff --git a/Cargo.lock b/Cargo.lock index 8552f30..1641084 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -110,6 +110,7 @@ dependencies = [ "base64", "num-bigint", "num-traits", + "pioneer-optical", "rand", "serde", "serde_json", @@ -207,6 +208,12 @@ version = "0.2.17" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" +[[package]] +name = "pioneer-optical" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "48a5bdb9e9a07422ceb5133d227fbe4ecce77060007e5a9bac0dda3d82d72911" + [[package]] name = "proc-macro2" version = "1.0.107" diff --git a/Cargo.toml b/Cargo.toml index 94ba411..086fda6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -30,6 +30,8 @@ rand = "0.10" # material on drop so it doesn't linger in freed memory (defence in depth atop # the redacting Debug impls). `derive` gives ZeroizeOnDrop for the key structs. zeroize = { version = "1", features = ["derive"] } +# Pioneer/Renesas vendor CDB constructors (identity read, gated memory read, knock). +pioneer-optical = "0.7" [lints.clippy] uninlined_format_args = "allow" diff --git a/src/renesas/mod.rs b/src/renesas/mod.rs index 677d965..3dc9955 100644 --- a/src/renesas/mod.rs +++ b/src/renesas/mod.rs @@ -9,9 +9,8 @@ use crate::scsi::{DataDirection, ScsiTransport, is_dead_bus}; use crate::{UnlockCtx, UnlockError, Unlocked, Unlocker}; -/// READ_BUFFER mode 0x02, buffer 0xF1 — the Renesas vendor identity buffer. -const RB_F1_CDB: [u8; 10] = [0x3C, 0x02, 0xF1, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x00]; -const RB_F1_LEN: usize = 48; +/// The Renesas vendor identity block length (READ_BUFFER 0x02/0xF1). +const RB_F1_LEN: usize = pioneer_optical::IDENTITY_LEN as usize; /// The ASCII interface marker a Renesas controller returns at `[16..19]`. const RENESAS_MARKER: &[u8] = b"SAT"; const RENESAS_MARKER_OFFSET: usize = 16; @@ -27,7 +26,8 @@ const RENESAS_MARKER_OFFSET: usize = 16; /// `Err(Transport)` on a dead bus. pub fn is_renesas(scsi: &mut dyn ScsiTransport) -> std::result::Result { let mut buf = [0u8; RB_F1_LEN]; - match scsi.execute(&RB_F1_CDB, DataDirection::FromDevice, &mut buf, 5_000) { + let cdb = pioneer_optical::vendor_identity(); + match scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000) { Ok(r) => { let end = RENESAS_MARKER_OFFSET + RENESAS_MARKER.len(); Ok(r.status == 0 @@ -72,27 +72,32 @@ impl Renesas { // MakeMKV's vendor "open" sequence: primary read (A), and on refusal a // knock + second read (B). fn vendor_open(scsi: &mut dyn ScsiTransport) -> std::result::Result { - const RB_B0_04_CDB: [u8; 10] = [0x3C, 0x02, 0xB0, 0x00, 0x00, 0x04, 0x00, 0x00, 0xA4, 0x00]; - const KNOCK_A5AAAA_CDB: [u8; 10] = - [0x3B, 0x02, 0x41, 0xA5, 0xAA, 0xAA, 0x00, 0x00, 0x00, 0x00]; - const RB_B0_500000_CDB: [u8; 10] = - [0x3C, 0x02, 0xB0, 0x50, 0x00, 0x00, 0x00, 0x00, 0xA4, 0x00]; - // A: primary open read. - if read_is_good(scsi, &RB_B0_04_CDB)? { + if read_is_good(scsi, &pioneer_optical::read_memory(0x04, OPEN_READ_LEN))? { return Ok(true); } // B: MakeMKV's fallback — knock, then the second-window read. The knock is // fire-and-forget (payload-less); its own status is not the signal. - match scsi.execute(&KNOCK_A5AAAA_CDB, DataDirection::None, &mut [], 5_000) { + match scsi.execute( + &pioneer_optical::knock(), + DataDirection::None, + &mut [], + 5_000, + ) { Ok(_) => {} Err(e) if is_dead_bus(&e) => return Err(UnlockError::Transport), Err(_) => {} // a drive that refuses the knock still gets the B read tried } - read_is_good(scsi, &RB_B0_500000_CDB) + read_is_good( + scsi, + &pioneer_optical::read_memory(0x50_0000, OPEN_READ_LEN), + ) } } +/// The vendor "open" reads' length (0xA4 = 164 bytes). +const OPEN_READ_LEN: u32 = 0xA4; + /// Issue a 164-byte vendor READ_BUFFER; `Ok(true)` on GOOD status, `Ok(false)` /// on CHECK CONDITION (drive refused), `Err(Transport)` on a senseless /// transport failure (dead bus). @@ -100,7 +105,7 @@ fn read_is_good( scsi: &mut dyn ScsiTransport, cdb: &[u8; 10], ) -> std::result::Result { - let mut buf = [0u8; 164]; + let mut buf = [0u8; OPEN_READ_LEN as usize]; match scsi.execute(cdb, DataDirection::FromDevice, &mut buf, 5_000) { Ok(r) => Ok(r.status == 0), Err(e) => {