mirror of
https://github.com/freemkv/freemkv-unlock.git
synced 2026-10-07 18:13:11 +00:00
Compare commits
6
Commits
v1.7.1-rc7
...
v1.7.3
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
98ef26f78c | ||
|
|
93d9517964 | ||
|
|
64a53b8349 | ||
|
|
f434298e0d | ||
|
|
a18bc90164 | ||
|
|
79f3324488 |
@@ -1,7 +1,17 @@
|
||||
# Changelog
|
||||
|
||||
## [1.7.3] — 2026-09-19
|
||||
|
||||
### Changed
|
||||
|
||||
- firmware: mirror the fw 0.8.3 RESET ABI — added `RESET_TO_DEFAULTS` (`0x01`, restore the baked create-time defaults into RAM) and `FirmwareControl::reset_to_defaults()`; `RESET_TO_OEM` (`0xFF`) is now documented as TRUE OEM (RAM passthrough **and** the NV block blanked to all-`0xFF`, so the drive is traceless), and `RESET_TO_FLASH` as marker-gated (a never-saved drive loads the defaults). Wire values unchanged.
|
||||
|
||||
## [1.7.2] — UNRELEASED
|
||||
|
||||
### Fixed
|
||||
|
||||
- `FreemkvUnlocker::set` now issues the vendor SET over the required 64-byte data-in phase (mirroring `FirmwareControl::set`). freemkv firmware 0.8.x aborts a SET sent with no data phase (CHECK CONDITION / Aborted Command), so every drive-feature SET failed — including the load-bearing `Set(Ake, off)` — and the freemkv firmware unlock silently fell back to the AACS/online-key route. Drives on freemkv firmware now unlock via the freemkv route again (hardware-confirmed on BU40N fw 0.8.1).
|
||||
|
||||
### Changed
|
||||
|
||||
- firmware: mirror ABI v2 — `Save` + `RESET`-to-flash/OEM modes, the region/speed value encodings, and the HRL/AKE/BUS `off = unlock` (`0x00`) polarity; `full_unlock`'s default profile flipped to match. Aligns `FirmwareControl` with freemkv firmware 0.8.0.
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "freemkv-unlock"
|
||||
version = "1.7.1"
|
||||
version = "1.7.3"
|
||||
edition = "2024"
|
||||
rust-version = "1.88"
|
||||
license = "MIT"
|
||||
|
||||
+36
-11
@@ -31,7 +31,9 @@
|
||||
//!
|
||||
//! Every feature defaults to [`STATE_PASSTHROUGH`] (firmware does not touch the
|
||||
//! subsystem → byte-identical to OEM); [`Verb::Reset`] with [`RESET_TO_OEM`]
|
||||
//! returns every feature to passthrough, while [`RESET_TO_FLASH`] reloads the last
|
||||
//! returns every feature to passthrough (and, firmware ≥0.8.3, blanks the NV block
|
||||
//! so the drive is traceless), [`RESET_TO_DEFAULTS`] restores the baked create-time
|
||||
//! defaults, while [`RESET_TO_FLASH`] reloads the last
|
||||
//! [`Verb::Save`]d config block.
|
||||
|
||||
use crate::scsi::{DataDirection, ScsiTransport};
|
||||
@@ -114,11 +116,21 @@ pub const REGION_FREE: u8 = 0x0F;
|
||||
|
||||
/// [`Verb::Reset`] mode (rides in the state slot `cdb[6]`): reload the saved flash
|
||||
/// config block into the RAM feature-state table, discarding un-saved RAM changes
|
||||
/// — the non-destructive "revert to last SAVE".
|
||||
/// — the non-destructive "revert to last SAVE". Marker-gated in firmware ≥0.8.3: a
|
||||
/// never-saved drive loads the baked create-time defaults instead.
|
||||
pub const RESET_TO_FLASH: u8 = 0x00;
|
||||
|
||||
/// [`Verb::Reset`] mode (rides in the state slot `cdb[6]`): force every feature to
|
||||
/// [`STATE_PASSTHROUGH`] (OEM behaviour everywhere), regardless of the saved config.
|
||||
/// [`Verb::Reset`] mode (rides in the state slot `cdb[6]`): restore the baked
|
||||
/// per-create **defaults** (the create-time flags, e.g. UHD/BD on) into the RAM
|
||||
/// feature-state table. RAM-only — does not touch flash. This is what a never-saved
|
||||
/// drive boots to. Firmware ≥0.8.3.
|
||||
pub const RESET_TO_DEFAULTS: u8 = 0x01;
|
||||
|
||||
/// [`Verb::Reset`] mode (rides in the state slot `cdb[6]`): TRUE OEM. Forces every
|
||||
/// feature to [`STATE_PASSTHROUGH`] in RAM **and** (firmware ≥0.8.3) writes the NV
|
||||
/// config block all-`0xFF` — marker included — so the drive is byte-for-byte like a
|
||||
/// never-saved/never-configured drive (no trace it was ever set). Because the marker
|
||||
/// is then `0xFF`, the NEXT boot loads the baked defaults, exactly like a fresh drive.
|
||||
pub const RESET_TO_OEM: u8 = 0xFF;
|
||||
|
||||
/// The verb selector in `cdb[4]`. These numeric values ARE the wire protocol.
|
||||
@@ -133,8 +145,10 @@ pub enum Verb {
|
||||
Get = 0x03,
|
||||
/// Restore the RAM feature-state table. The mode rides in `cdb[6]` (the state
|
||||
/// slot): [`RESET_TO_FLASH`] (`0x00`) reloads the saved flash config block back
|
||||
/// into RAM (discarding un-saved changes), [`RESET_TO_OEM`] (`0xFF`) forces every
|
||||
/// feature to [`STATE_PASSTHROUGH`] (out-of-the-box behaviour). Ignores feature.
|
||||
/// into RAM (discarding un-saved changes; marker-gated in fw ≥0.8.3),
|
||||
/// [`RESET_TO_DEFAULTS`] (`0x01`) restores the baked create-time defaults, and
|
||||
/// [`RESET_TO_OEM`] (`0xFF`) forces every feature to [`STATE_PASSTHROUGH`] AND
|
||||
/// blanks the NV block (traceless, fw ≥0.8.3). Ignores feature.
|
||||
Reset = 0x04,
|
||||
/// Diagnostic RAM peek: [`MEMREAD_LEN`] bytes at the 32-bit address in
|
||||
/// `cdb[5..9]` (big-endian).
|
||||
@@ -267,8 +281,9 @@ pub fn build_get_cdb(feature: Feature) -> [u8; CDB_LEN] {
|
||||
|
||||
/// Build a `RESET` CDB. `mode` rides in the state slot (`cdb[6]`):
|
||||
/// [`RESET_TO_FLASH`] (`0x00`) reloads the saved flash config into RAM,
|
||||
/// [`RESET_TO_OEM`] (`0xFF`) forces every feature to passthrough. Floors its
|
||||
/// data-in at [`MIN_ALLOC_LEN`] for the same HW min-transfer reason as
|
||||
/// [`RESET_TO_DEFAULTS`] (`0x01`) restores the baked create-time defaults, and
|
||||
/// [`RESET_TO_OEM`] (`0xFF`) forces every feature to passthrough (and blanks NV).
|
||||
/// Floors its data-in at [`MIN_ALLOC_LEN`] for the same HW min-transfer reason as
|
||||
/// [`build_set_cdb`].
|
||||
pub fn build_reset_cdb(mode: u8) -> [u8; CDB_LEN] {
|
||||
build_cdb(Verb::Reset, None, Some(mode), MIN_ALLOC_LEN)
|
||||
@@ -528,14 +543,24 @@ impl<'a> FirmwareControl<'a> {
|
||||
self.exec_in(&cdb).map(|_| ())
|
||||
}
|
||||
|
||||
/// Restore every feature to [`STATE_PASSTHROUGH`] (RESET with
|
||||
/// [`RESET_TO_OEM`]). Data-in phase for the same reason as [`set`](Self::set) —
|
||||
/// a no-data RESET is aborted.
|
||||
/// Restore every feature to [`STATE_PASSTHROUGH`] (RESET with [`RESET_TO_OEM`])
|
||||
/// — TRUE OEM: firmware ≥0.8.3 also blanks the NV block so the drive is
|
||||
/// traceless. Data-in phase for the same reason as [`set`](Self::set) — a no-data
|
||||
/// RESET is aborted.
|
||||
pub fn reset(&mut self) -> Result<()> {
|
||||
let cdb = build_reset_cdb(RESET_TO_OEM);
|
||||
self.exec_in(&cdb).map(|_| ())
|
||||
}
|
||||
|
||||
/// Restore the baked create-time defaults into the RAM feature-state table
|
||||
/// (RESET with [`RESET_TO_DEFAULTS`]) — what a never-saved drive boots to; does
|
||||
/// not touch flash. Firmware ≥0.8.3. Data-in phase for the same reason as
|
||||
/// [`reset`](Self::reset).
|
||||
pub fn reset_to_defaults(&mut self) -> Result<()> {
|
||||
let cdb = build_reset_cdb(RESET_TO_DEFAULTS);
|
||||
self.exec_in(&cdb).map(|_| ())
|
||||
}
|
||||
|
||||
/// Reload the saved flash config block back into the RAM feature-state table
|
||||
/// (RESET with [`RESET_TO_FLASH`]), discarding any un-saved RAM changes — the
|
||||
/// non-destructive "revert to last SAVE". Data-in phase for the same reason as
|
||||
|
||||
@@ -44,6 +44,7 @@ fn state_and_frame_constants_match_abi() {
|
||||
assert_eq!(REGION_DVD_BASE, 0x00);
|
||||
assert_eq!(REGION_FREE, 0x0F);
|
||||
assert_eq!(RESET_TO_FLASH, 0x00);
|
||||
assert_eq!(RESET_TO_DEFAULTS, 0x01);
|
||||
assert_eq!(RESET_TO_OEM, 0xFF);
|
||||
}
|
||||
|
||||
@@ -332,6 +333,16 @@ fn reset_sends_reset_cdb_and_clears_state() {
|
||||
assert_eq!(m.states, FeatureStates::all_passthrough());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_to_defaults_sends_the_defaults_mode_cdb() {
|
||||
let mut m = FwMock::new();
|
||||
{
|
||||
let mut fw = FirmwareControl::new(&mut m);
|
||||
fw.reset_to_defaults().expect("reset to defaults");
|
||||
}
|
||||
assert_eq!(m.cdbs[0], build_reset_cdb(RESET_TO_DEFAULTS));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn save_then_reset_to_flash_restores_the_saved_value() {
|
||||
// SET a feature, SAVE it, force all-passthrough with RESET(to-OEM), then
|
||||
|
||||
+6
-6
@@ -18,8 +18,8 @@
|
||||
//! the migrated spec the HRL/AKE/BUS unlock direction is `STATE_OFF`, not `0x01`.
|
||||
|
||||
use crate::firmware::{
|
||||
Feature, MEMREAD_LEN, REGION_FREE, RESP_MAGIC, SPEED_MAX, STATE_OFF, build_identity_cdb,
|
||||
build_memread_cdb, build_set_cdb,
|
||||
Feature, MEMREAD_LEN, MIN_ALLOC_LEN, REGION_FREE, RESP_MAGIC, SPEED_MAX, STATE_OFF,
|
||||
build_identity_cdb, build_memread_cdb, build_set_cdb,
|
||||
};
|
||||
use crate::scsi::{DataDirection, ScsiTransport};
|
||||
use crate::{UnlockCtx, UnlockError, Unlocked, Unlocker};
|
||||
@@ -99,10 +99,10 @@ impl FreemkvUnlocker {
|
||||
state: u8,
|
||||
) -> std::result::Result<(), UnlockError> {
|
||||
let cdb = build_set_cdb(feature, state);
|
||||
// SET needs the 64-byte data-in phase its CDB advertises — the drive aborts
|
||||
// a no-data SET (CHECK CONDITION / Aborted Command, HW-confirmed on BU40N fw
|
||||
// 0.8.1). Mirror FirmwareControl::set (exec_in): read the table back, ignore it.
|
||||
let mut buf = [0u8; RESP_LEN];
|
||||
// SET needs the data-in phase its CDB advertises — a no-data SET is aborted
|
||||
// (CHECK CONDITION, HW-confirmed on BU40N fw 0.8.1). Buffer sized from the SAME
|
||||
// const the CDB advertises (MIN_ALLOC_LEN) so the transfer length has one source.
|
||||
let mut buf = [0u8; MIN_ALLOC_LEN as usize];
|
||||
match scsi.execute(&cdb, DataDirection::FromDevice, &mut buf, 5_000) {
|
||||
Ok(r) if r.status == 0 => Ok(()),
|
||||
Ok(r) => {
|
||||
|
||||
Reference in New Issue
Block a user