MattJackson
a74d395f68
Audit v3 fixes: all 3 tiers (19 findings)
...
Tier 1 (compilation + correctness):
- Fix nightly-only is_multiple_of → % 2 != 0 (stable Rust compat)
- Fix parse_sample_rate: check 192 before 96 (was returning wrong rate)
- macOS drive discovery: split unix.rs → linux.rs + macos.rs
- Linux: EACCES returns DevicePermission not DeviceNotFound
- CLI pipe.rs: Ctrl+C signal handler added
Tier 2 (correctness + security):
- MkvStream: reset demuxer after scanning→streaming transition
- Windows SPTI: zero data buffer before ioctl
- AACS cert verification: documented why silently skipped
- KEYDB: HOME + USERPROFILE fallback for Windows
- Library modules: pub(crate) for internal modules
- AACS: explicit re-exports, AES primitives pub(crate)
Tier 3 (performance + polish):
- IsoStream: batch 64-sector reads (was 1 sector at a time)
- DiscStream: buffer swap instead of copy in decrypt_and_buffer
- Vec capacity hints in TS/PS demuxer hot paths
- NetworkStream: TLS warning documented
- Batch rip: per-title progress display
- cargo fmt: 0 violations
319 tests, 0 fmt violations.
2026-04-11 19:24:25 +00:00
MattJackson
43f81e4eae
Fix audit v2 criticals: DiscStream read loop, CSS crack, ISO writer
...
Critical fixes:
- DiscStream: persistent read state (was creating new ContentReader per call)
Full error recovery, AACS/CSS decryption, extent tracking across reads
- CSS crack: labeled 'outer continue (was targeting wrong loop)
- CSS crack: LFSR0 polynomial fixed to match cipher (shifts 8,1,3,7)
- CSS lfsr: operator precedence clarified in LFSR0 init
Warning fixes:
- ISO writer: UDF tag checksums computed (was zeros)
- DVD extents: saturating_add for overflow safety
- Removed dead fields: HandshakeResult.error, ContentReader.content_format
- Added TODO for ISO long_ad >4GB support
319 tests, 20 clippy warnings remaining.
2026-04-11 19:11:25 +00:00
MattJackson
d56dd934bb
cargo fmt + clippy --fix: 104 format violations fixed, 8 clippy auto-fixes
2026-04-11 19:10:20 +00:00
MattJackson
c91d6e371f
Chapters, DVD subtitle palette, MKV track flags, progress total_bytes
...
Chapters:
- MPLS PlayList marks parsed (mark_type 1 = chapter)
- Chapter struct on DiscTitle (time_secs, name)
- MKV Chapters element with EditionEntry/ChapterAtom per mark
- 3 MPLS mark tests + 2 MKV chapter tests
DVD subtitle palette:
- IFO palette extraction (PGC offset 0xA4, 16 × YCbCr colors)
- YCbCr→RGB conversion for VobSub .idx format
- DvdSubParser codec_private returns formatted palette
- codec_data field on SubtitleStream flows through pipeline
- 5 palette tests (YCbCr conversion, formatting, overflow)
MKV track flags:
- FlagDefault: primary video/audio = 1, secondary = 0
- FlagForced: forced subtitles = 1
- Language: set from stream language code
- Already implemented, verified with 4 new tests
Progress total_bytes:
- IOStream trait: total_bytes() -> Option<u64>
- DiscStream, IsoStream: from disc_title.size_bytes
- M2tsStream, MkvStream: from file metadata on open
- NetworkStream, StdioStream, NullStream: None
316 tests total, all passing.
2026-04-11 17:43:47 +00:00
MattJackson
9cb0c369e4
Add MKV muxer, IsoWriter, disc pipeline, and network tests
...
- MKV muxer: EBML header, segment, cluster, cues, multi-track, keyframe flags — 6 tests
- MkvStream: BD-TS roundtrip, metadata preservation — 2 tests
- IsoWriter: valid UDF, file size update, custom names, empty content — 4 tests
- Disc pipeline: format detection (UHD/BD/DVD), content format, capacity, duration — 5 tests
- Network: listen/connect roundtrip, metadata flow — 2 tests (ignored for CI)
- Encryption: no AACS dir, no keydb — 2 tests
- 297 tests total, all passing
2026-04-11 17:27:36 +00:00
MattJackson
63accb6718
100% codec coverage + disc/ and aacs/ module refactors
...
Codec coverage (DVD + BD + UHD):
- E-AC-3 (Dolby Digital Plus): bsid detection, frame size calc — 8 tests
- DTS-HD MA/HR: extension substream (0x64582025) detection — 8 tests
- LPCM: BD header skip, raw PCM extraction — 6 tests
- DVD VobSub subtitles: passthrough parser — 5 tests
- Dolby Vision: verified RPU NAL type 62 preserved in HEVC — 1 test
Module refactors:
- disc.rs → disc/mod.rs + bluray.rs + dvd.rs + encrypt.rs
- aacs/mod.rs (1661 lines) → mod.rs (21) + keydb.rs + keys.rs + decrypt.rs
- All public APIs preserved, all tests pass
270 tests total, 0 failures.
2026-04-11 17:22:18 +00:00
MattJackson
187255106f
Add crypto roundtrip tests: CSS + AACS validation
...
- CSS: decrypt_key determinism, descramble XOR roundtrip, TAB1 permutation,
TAB4 bit-reversal involution, Stevenson attack on scrambled sector
- AACS: decrypt_unit roundtrip, disc hash deterministic, VUK derivation,
unit key parsing, EC point-on-curve and ECDSA already covered
- 239 tests total
2026-04-11 17:17:29 +00:00
MattJackson
fd4c88e969
CSS crypto tests + DVD pipeline fully wired
...
- CSS roundtrip tests: decrypt_key determinism, descramble XOR roundtrip
- CSS table verification: TAB1 is permutation, TAB4 is bit-reversal involution
- DVD scan pipeline confirmed: scan_dvd_titles, CSS crack, ContentReader descramble
- 229 tests, all passing
2026-04-11 17:14:58 +00:00
MattJackson
59e6916702
Rewrite CSS from Stevenson 1999 paper — proper table-driven cipher
...
- tables.rs: 5 CSS specification tables (TAB1-TAB5, mathematical constants)
- lfsr.rs: Table-driven LFSR1 (TAB2/TAB3) + LFSR0 (TAB4), sector seed XOR,
decrypt_key() mangling function, descramble_sector() with proper feedback
- crack.rs: Stevenson divide-and-conquer attack (2^16 LFSR1 iteration,
LFSR0 deduction from known plaintext, 10-byte validation)
- No external code copied — original Rust implementation from the 1999 paper
- 225 tests, 0 ignored
2026-04-11 17:07:21 +00:00
MattJackson
01235ff347
Audit fixes + DVD support foundation (IFO, PS demux, MPEG-2, CSS crack)
...
Audit fixes (14 critical, 22 warnings):
- UDF: bounds checks on all ICB/FID parsing from disc data
- SCSI Linux: saturating_sub on residual, CDB length guard, buffer size guard
- SCSI macOS: SCSITaskStatus u32 (was u8 — stack corruption)
- AACS: EC mod_inv returns infinity instead of panic, key reduced mod n
- AACS: do_handshake tries all host certs (was returning on first failure)
- H.264: bounds check on SPS < 4 bytes
- ContentReader: error on missing unit key (was zero-fill)
- KEYDB: flat redirect loop (was recursive), 100MB response limit, Windows HOME fallback
- ISO writer: AVDP extent order, partition length, allocation cap
- Network: removed TCP_NODELAY on bulk stream
- MKV: guard on u64::MAX seek
- disc.rs: saturating_sub on extent offset, simplified dead region code
- cargo fmt (610 violations), cargo clippy --fix (55 auto-fixes)
DVD support (new files):
- src/ifo.rs — IFO parser (VIDEO_TS.IFO, VTS_XX_0.IFO, PGC chains, cells, streams) — 13 tests
- src/mux/ps.rs — MPEG-2 Program Stream demuxer (pack headers, PES, private stream 1) — 12 tests
- src/mux/codec/mpeg2.rs — MPEG-2 video parser (sequence headers, I-frame detection) — 15 tests
- src/css/crack.rs — split-attack algorithm (LFSR cipher needs verification — test ignored)
226 tests total (was 186), 1 ignored (CSS crack needs cipher verification).
2026-04-11 16:52:22 +00:00
MattJackson
6de4ee4b5c
v0.7.2: Windows SPTI, 177 tests, platform file separation
2026-04-11 16:15:14 +00:00
MattJackson
425583ea09
Refactor: drive discovery into platform files, no inline cfg
...
- drive/unix.rs: find_drives() + resolve_device() for Linux/macOS
- drive/windows.rs: find_drives() + resolve_device() + normalize_path() for Windows
- drive/mod.rs: clean delegation, no cfg branches
- scsi/windows.rs: SPTI transport only, no drive discovery
2026-04-11 16:12:53 +00:00
MattJackson
f67a8aa8d5
Add Windows SPTI backend, CI check, platform support complete
...
- scsi/windows.rs: SCSI_PASS_THROUGH_DIRECT via DeviceIoControl
- Device path normalization (D:, \\.\CdRom0, \\.\D:)
- Windows drive discovery (CdRom0-15 + drive letters)
- CI: cargo check on windows-latest
- Platform table: Linux + macOS + Windows all supported
2026-04-11 16:08:30 +00:00
MattJackson
059bce6331
Add 113 tests, update CI to checkout@v5, add FEATURES.md
...
Test suite: 64 → 177 tests
- MPLS parser: 6 tests (synthetic binary, streams, errors)
- CLPI parser: 6 tests (EP map, PTS/SPN math, errors)
- H.264: 12 tests (NAL parsing, SPS/PPS, keyframes)
- HEVC: 13 tests (VPS/SPS/PPS, IRAP range, codec private)
- AC3: 12 tests (syncword, frame extraction)
- VC1: 15 tests (BITMAPINFOHEADER, start codes)
- DTS: 5, TrueHD: 4, PGS: 4 tests
- EBML: 6 tests (size/ID/string/float roundtrips)
- UDF: 10 tests (MockSectorReader, filesystem parsing, error paths)
- Disc: 8 tests (scan_image, DiscTitle helpers)
- Streams: 5 new (meta roundtrip, MkvStream)
- NullStream: 4, StdioStream: 2, IsoSectorReader: 2
CI: actions/checkout@v4 → v5 (all workflows)
FEATURES.md: created for v0.7.1
2026-04-11 16:02:49 +00:00
MattJackson
cb099531c9
v0.7.1: SectorReader trait, IsoStream, StdioStream, resolve_encryption
...
- SectorReader trait decouples disc scanning from SCSI
- Disc::scan_image() for ISO and any sector source
- resolve_encryption() handles AACS 1.0/2.0/none in one path
- IsoStream: full UDF/MPLS/CLPI/labels pipeline from ISO files
- StdioStream: stdin/stdout pipe
- Strict scheme:// URL format with validation
- Labels module refactored to SectorReader
- 7 stream types total
2026-04-11 15:49:29 +00:00
MattJackson
c79d3ab530
Fix update-readme: use ORG_DISPATCH_TOKEN to bypass branch protection
2026-04-11 15:04:22 +00:00
MattJackson
0cb6718238
Bump to v0.7.0
2026-04-11 14:55:52 +00:00
MattJackson
1d46ced137
Add StdioStream, IsoStream; enforce scheme:// URL format
...
- StdioStream: stdin/stdout pipe, format-agnostic
- IsoStream: read BD-TS from Blu-ray ISO images
- URL resolver: bare paths rejected, all URLs require scheme:// prefix
- Validation: empty paths, missing ports, read-only/write-only errors
- Tests: 22 passing (URL parsing, validation, metadata roundtrip)
- Docs: full stream table with 7 stream types
2026-04-11 14:54:28 +00:00
MattJackson
bc9bf022ea
Add IOStream trait and stream-based I/O architecture
...
Introduce IOStream trait for uniform read/write across disc, file,
network, and null streams. Rename Title→DiscTitle, add stream URL
resolver, split old stream.rs into focused modules (m2ts, mkvstream,
network, disc, null, resolve, meta).
2026-04-10 19:13:53 -07:00
MattJackson
026b6e7a43
Support multiple host certs, expose handshake_error on AacsState
...
- host_cert: Option → host_certs: Vec (try all until one succeeds)
- handshake_error field shows why auth failed (e.g. cert rejected/revoked)
2026-04-10 09:48:29 -07:00
MattJackson
914891feef
Bump to v0.6.1 — republish with open() all drives, AACS 2.0, has_profile()
2026-04-10 09:30:15 -07:00
MattJackson
b8b00f2121
v0.6.0: changelog, open() all drives, AACS 2.0, MKV muxer
2026-04-10 09:26:45 -07:00
MattJackson
dd9f10439c
Update README for v0.6.0: AACS 2.0 done, open() all drives, .cargo gitignore
2026-04-10 09:24:01 -07:00
MattJackson
a07b2a85a4
Remove orphaned jar.rs — replaced by labels/ module
2026-04-10 09:17:11 -07:00
MattJackson
c3e50f3a2e
open() works on all drives, AACS 2.0 handshake wired, raw_gc_010c on DriveId
...
- DriveSession::open() no longer requires profile match — works on any optical drive
- init()/probe_disc() return error gracefully for unknown drives
- find_drives() returns all optical drives (PDT 0x05), not just profile-matched
- has_profile() check for callers
- AACS 2.0: handshake wired into resolve_aacs() — real VID + read_data_key
- DriveId: added raw_gc_010c field for GET_CONFIG 010C response bytes
2026-04-10 08:48:41 -07:00
MattJackson
5990ec9566
Fix test failures: correct EBML size assertion, fix doc tests
...
- Fix write_size test: 127 encodes as [0x40, 0x7F] not [0xC0, 0x7F]
- Mark doc examples as ignore (use incomplete pseudo-code)
2026-04-10 08:23:32 -07:00
MattJackson
01d26c8e33
Fix compiler warnings: remove unused imports and dead code
...
- Remove unused NAL_SLICE and NAL_SEI constants from h264 parser
- Remove unused aacs import from Disc::scan()
2026-04-10 08:21:55 -07:00
MattJackson
70c75ca374
Add design docs for API and MKV muxer architecture
2026-04-10 08:19:53 -07:00
MattJackson
0b014287a3
Add MKV muxer and event system
...
- Add event.rs: structured event system for progress reporting
- Add mux/: MKV muxer pipeline (EBML writer, TS demuxer, stream assembly)
- Codec parsers: H.264, HEVC, AC-3, DTS, TrueHD, PGS, VC-1
- Lookahead buffer for codec private data extraction
- Direct m2ts-to-MKV streaming without intermediate files
2026-04-10 08:19:40 -07:00
MattJackson
24345bc202
Refactor error types: replace generic AacsError/DiscError with typed variants
...
- Split AacsError { detail } into 13 specific error variants (AacsCertShort,
AacsAgidAlloc, AacsCertRejected, etc.) with unique error codes E7001-E7012
- Split DiscError { detail } into 7 specific variants (DiscRead, MplsParse,
ClpiParse, UdfNotFound, DiscNoTitles, DiscTitleRange, DiscNoExtents)
- Add WriteError (E5001), KeydbLoad (E8005), MuxLookahead (E9000), MuxWrite (E9001)
- Add OpenDisc API for single-call open+scan+rip workflow
- Remove all English text from error Display impl (code-only output)
- Normalize doc comments to use -- instead of em dash for ASCII consistency
2026-04-10 08:19:28 -07:00
MattJackson
ad592d244b
Code audit: fix unused variables, update docs
...
- Fix unused variable warnings in profile parsing (|e| -> |_|)
- Fix outdated 'freemkv info --share' reference in README (now drive-info)
2026-04-10 08:19:01 -07:00
MattJackson
5949bcee89
Remove SpeedTable, add probe_disc(), named constants, clean architecture
...
- Removed SpeedTable entirely — drive manages speeds after probe
- Renamed read_speed_table() → probe_disc()
- Named all SCSI constants: SUB_CMD_UNLOCK, SUB_CMD_INIT, SUB_CMD_PROBE,
INIT_ADDR_BD, INIT_ADDR_UHD, PROBE_COARSE_END, PROBE_FINE_END, etc.
- Auto-detect BD vs UHD from disc capacity for correct probe init address
- Fixed NOMINAL_SPEED_B (was invalid CDB, removed — single max instead)
- Added session.set_speed() for simple speed control
- Error recovery: re-init on first error, BD2x on repeated errors
- Batch size uses full kernel limit (was 80%, now 100%)
- Clean variant_a/variant_b with named constants
API: open() → wait_ready() → init() → probe_disc() → scan() → read
2026-04-09 15:17:25 -07:00
MattJackson
5c73d9d5a0
Speed table: generic zone-based speed management
...
- SpeedTable: maps disc positions to optimal speeds
- Default: max speed everywhere (drive manages itself)
- After read_speed_table(): calibrated per-zone speeds
- One u32 comparison per read on hot path
- Error recovery: reduce() / resume() override table temporarily
- Replaces old tier-based speed management in ContentReader
- MT1959 split into mod.rs + variant_a.rs + variant_b.rs
- PlatformDriver: init() + read_speed_table() + is_ready()
2026-04-09 13:33:02 -07:00
MattJackson
d7d13d2849
Update README: v0.6.0 API, real sample output
2026-04-09 13:03:00 -07:00
MattJackson
81a88dcad0
Profiles v2: chipset+variant top-level keys, minimal per-drive data
...
profiles.json: { "mt1959_a": [...], "mt1959_b": [...], "renesas": [] }
Each profile: identity + signature + firmware (3 fields)
Platform enum replaces Chipset — section determines variant
2026-04-09 12:50:44 -07:00
MattJackson
7c8c26af24
Bump to v0.6.0
2026-04-09 12:39:50 -07:00
MattJackson
ae973cb077
v0.6.0: Clean API, chipset-keyed profiles, streamlined platform driver
...
- API: open() is OEM-only, wait_ready() separate, init() optional
- Profiles: chipset-keyed JSON ({ "mt1959": [...], "renesas": [] })
- Profiles: identity group, variant + signature + firmware per drive
- Platform constants: mode, buffer_id, nominal speed, verify commands
moved from profiles to code (variant-determined, not per-drive)
- Removed unused fields: register CDBs, speed tables, status data
- Platform driver: unlock + firmware upload + calibrate + speed only
- Cross-compile fix: build.rs uses CARGO_CFG_TARGET_OS for framework linking
2026-04-09 12:38:02 -07:00
MattJackson
b1d1a17cfa
Update profiles: fix data extraction offset
...
Firmware payload was extracted 12 bytes too early — included handler
table pointers instead of actual microcode. Fixed by scanning for
function table position (VM address pattern) instead of magic offsets.
All 206 profiles regenerated. Cold boot firmware upload should now work.
2026-04-09 08:37:23 -07:00
MattJackson
6784829856
Update README and CHANGELOG for v0.5.0
...
- 12-23 MB/s read speeds, kernel transfer limit auto-detection
- Full custom firmware init pipeline (all 10 handlers)
- MPLS parser fixes (PGS language offset, secondary streams)
- 206 profiles with full per-drive data
2026-04-09 07:27:10 -07:00
MattJackson
7fcbcff6e5
Fix subtitle language parsing: PGS in audio slots, secondary PG entries
...
Two MPLS parsing issues:
1. PGS subtitle (0x90) appearing in audio stream slot: language was read
from audio offset sa[2..5] instead of PG offset sa[1..4], causing
truncation ("ng " instead of "eng"). Fixed by detecting PGS coding
type in audio slots and using correct offset.
2. Secondary PG (PiP subtitle) entries were not consumed, causing
position tracking to drift. Added missing n_pip_pg loop.
3. Added stream_type 5/6/7 attribute parsing for secondary audio/video.
2026-04-08 22:41:07 -07:00
MattJackson
ef38a90109
Fix read speed: detect kernel max transfer size, fix phantom streams
...
Root cause: READ_10 requested 510 sectors (1MB) but kernel sg driver
limits to max_hw_sectors_kb=120 (60 sectors). All bulk reads errored,
error handler fell to 3-sector reads → 4.8 MB/s instead of 12+ MB/s.
- detect_max_batch_sectors() now resolves sg→block device via sysfs
and uses 80% of kernel limit for safety margin
- Default fallback reduced from 510 to 48 sectors
- ContentReader uses per-device detected max, not hardcoded constant
- Filter out coding_type 0x00 (empty/padding) stream entries
- Add MPLS stream_type 5/6/7 attribute parsing (secondary audio/video)
Tested on BU40N: 4.8 → 12.5 MB/s sustained, 23.7 MB/s peak.
2026-04-08 22:30:08 -07:00
MattJackson
475cfadfc8
Lock down Platform trait: pub(crate), only init/set_read_speed/is_ready
...
Platform trait is no longer publicly exported. External code uses
DriveSession only — cannot call unlock, load_firmware, calibrate directly.
- Platform trait: pub(crate) with 3 methods only
- All handlers are private methods on Mt1959
- DriveStatus moved to mt1959 internal struct
- init() has guard: no re-init if already ready
- set_read_speed() has guard: no-op if not calibrated
- Removed open_unlocked() — open() is the only entry
- Removed Platform and DriveStatus from public exports
Prevents: out-of-sequence SCSI commands, double-init, wrong firmware writes.
2026-04-08 21:41:57 -07:00
MattJackson
97407570ed
Clean pipeline: one open, one init, no double-init
...
- Removed open_unlocked() — open() is the only entry
- Removed redundant init() call from open_title()
- init() called once in open(), handles everything
- Each function does one thing: open→init→scan→read
2026-04-08 21:35:48 -07:00
MattJackson
490c597995
Fix init() hang: make status() non-fatal, single attempt
...
status() (sub_cmd 0x13) returns ILLEGAL REQUEST on some drives.
Was retrying 6× with 30s timeouts = 180s hang during init().
Steps 1-10 all pass on hardware:
unlock: OK, load_firmware: OK, calibrate: OK,
register_a: OK, register_b: OK
Only status fails — not needed for reads.
2026-04-08 21:27:34 -07:00
MattJackson
c5b0ab3156
Fix PGS subtitle misclassified as audio in STN parsing
...
When MPLS STN table parsing drifts (disc-specific alignment issue),
a PGS subtitle entry (coding_type 0x90/0x91) can appear in the audio
stream section. Previously this showed as garbled "ng PGS 5.1" audio.
Fix: guard in stream builder checks if audio-typed streams have
subtitle codecs and reclassifies them as subtitles.
Also: unknown stream types now filtered out (filter_map) instead of
creating fake Video entries that showed as "?" in output.
Tested on V for Vendetta BD — "ng PGS 5.1" gone, clean output.
2026-04-08 21:14:39 -07:00
MattJackson
eb4d89b251
Bump to v0.5.0
2026-04-08 20:53:54 -07:00
MattJackson
bb9f996afc
Update profiles.json: 206 drives with complete data
...
Generated by: profile generator --profiles sdf0.bin keys.json --drive-db drive_profiles.json
206 profiles (66 A + 140 B), all with identity from brute-force dictionary.
No manual merging. One tool, three inputs, complete output.
2026-04-08 20:51:15 -07:00
MattJackson
766ffc70f6
Update profiles.json: 206 drives
...
New profile format includes all -verified fields:
- ld_microcode (base64): firmware payload for WRITE_BUFFER/MODE_SELECT
- hardware_register_a/b_cdb: pre-built 10-byte SCSI CDBs
- drive_nominal_speed_cdb: calibration SET_CD_SPEED
- speed_zone_table, speed_calc_table: operation speed constants
- unlock_init_value, unlock_response_size_minus_init
- drive_signature: per-drive unlock check
206 profiles (66 mt1959_a + 140 mt1959_b), 661KB.
Identity fields merged from drive database.
All 32 tests pass.
2026-04-08 20:46:40 -07:00
MattJackson
91d813ab51
Fix variant B firmware upload: MODE SELECT not WRITE_BUFFER
...
B firmware upload () byte-level verification reveals:
- Step 1: MODE SELECT (0x55), NOT WRITE_BUFFER — sends 2496 bytes (0x9C0)
- Step 2: Check result == 2
- Step 3: READ_BUFFER mode=6 offset=0x3000 (16B firmware metadata)
- Step 4: WRITE_BUFFER mode=6 (16B from fw_write_data)
- Step 5: Vendor verify CDB (0xF1 opcode from blob)
- Step 6: do_unlock × 5 retries + 1 confirmation
Key differences from A:
- A uses WRITE_BUFFER (0x3B), B uses MODE SELECT (0x55)
- A sends 1888 bytes, B sends 2496 bytes
- B has extra READ metadata + WRITE 16B + vendor verify steps
- B retries unlock 5 times (A does 2)
Added profile fields: fw_write_data (16B), verify_cdb (10B) for B-only.
2026-04-08 20:40:37 -07:00
MattJackson
3ee0f0cedf
mt1959: separate A/B firmware upload paths
...
A (): single WRITE_BUFFER → verify 0x45 → unlock×2
B (): WRITE handshake → READ 0x3000 → WRITE 16B → verify → unlock×5
9/10 handlers are identical A/B. Only load_firmware has different logic.
Both paths end with do_unlock() — firmware upload is a prerequisite for
unlock, not a substitute. init() tries unlock first, falls back to
load_firmware only on failure (cold boot).
2026-04-08 20:35:43 -07:00