Commit Graph
100 Commits
Author SHA1 Message Date
MattJackson dc4ebd7d9b 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 168005ef34 Fix update-readme: use ORG_DISPATCH_TOKEN to bypass branch protection 2026-04-11 15:04:22 +00:00
MattJackson 6dc308f245 Bump to v0.7.0 2026-04-11 14:55:52 +00:00
MattJackson cccb6b4631 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 8c2f3898b8 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 37c98e8826 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 891dee3db9 Bump to v0.6.1 — republish with open() all drives, AACS 2.0, has_profile() 2026-04-10 09:30:15 -07:00
MattJackson e34b41967d v0.6.0: changelog, open() all drives, AACS 2.0, MKV muxer 2026-04-10 09:26:45 -07:00
MattJackson c063dbd76e Update README for v0.6.0: AACS 2.0 done, open() all drives, .cargo gitignore 2026-04-10 09:24:01 -07:00
MattJackson d1f8db93d6 Remove orphaned jar.rs — replaced by labels/ module 2026-04-10 09:17:11 -07:00
MattJackson 926939f24b 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 48d70c092c 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 c14b319212 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 55b22bfb9d Add design docs for API and MKV muxer architecture 2026-04-10 08:19:53 -07:00
MattJackson 48b212c9d2 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 074f21ba58 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 9241d767ee 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 65996ade59 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 09f5bb7816 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 12a5985a58 Update README: v0.6.0 API, real sample output 2026-04-09 13:03:00 -07:00
MattJackson d23632931b 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 92e7692779 Bump to v0.6.0 2026-04-09 12:39:50 -07:00
MattJackson 0c11623666 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 b454d100c6 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 097473d371 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 5e3157b18d 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 d9c5f7a0d2 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 6456e24bb5 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 0b19154bd3 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 ecc6cd9f6b 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 71a21b5c6f 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 79b1b4d5b5 Bump to v0.5.0 2026-04-08 20:53:54 -07:00
MattJackson 85b38de4a7 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 686bf91bb5 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 f07f8210c3 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 4153d23652 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
MattJackson 760bab0893 mt1959.rs: complete platform implementation
Every handler traced instruction-by-instruction from operation: do_unlock with configurable response size
operation: WRITE_BUFFER + verify buf=0x45 + unlock×2
operation: do_unlock → validate → send pre-built CDB → [4:20]
operation: same with CDB B
operation: init → scan 0x0000-0x5800 → build table → triple speed
operation: ↔x86 VM only (host_write 16B), no SCSI
operation: do_unlock → validate → probe 0x13 → check sig → features
operation: 3 paths by param count (1/5/9), dynamic READ_BUFFER
operation: search 64-entry table → position probe →
 set_cd_speed_max → custom SET_CD_SPEED with matched value
operation: ↔x86 VM only (host_read 8B), no SCSI

init() matches x86 dispatch exactly:
 Phase 1: unlock → [load_fw] × 6
 Phase 2: calibrate × 6
 Phase 3: probe (drive info)
 Phase 4: register A + B × 5
 Phase 5: status × 6

Handlers 5/9 are VM communication (no SCSI equivalent in Rust).
All other handlers send real SCSI commands.
2026-04-08 20:30:46 -07:00
MattJackson ce8ddb48bb DriveProfile with all per-drive fields, drive.rs uses init() as single entry
DriveProfile now has every field traced from firmware:
- drive_signature, unlock_init_value, unlock_response_size_minus_init
- ld_microcode (base64, ~1888B firmware payload)
- hardware_register_a_cdb, hardware_register_b_cdb (10B pre-built CDBs)
- drive_nominal_speed_cdb (12B calibration speed)
- speed_zone_table (28B), speed_calc_table (25B)

drive.rs simplified:
- open() calls init() instead of unlock()
- init() is the ONLY entry point — handles full dispatch sequence internally
- Removed read_config, read_register, maintain_speed, read_sectors from public API
- Added set_read_speed() for per-zone speed during content reads
- disc.rs updated to call init() instead of unlock()

Compiles clean, all tests pass.
2026-04-08 20:15:25 -07:00
MattJackson 377cbe0aec Rewrite mt1959.rs: complete platform driver with full profile support
Complete rewrite of MT1959 platform driver:
- All 10 handlers implemented matching firmware logic 1:1
- load_firmware(): WRITE_BUFFER ld_microcode on cold boot
- calibrate(): full zone probe + speed table + triple SET_CD_SPEED
- init(): x86 dispatch sequence (unlock → fw × 6, calibrate × 6)
- read_register_a/b(): use pre-built CDBs from profile
- set_read_speed(): speed table lookup per zone
- status(), probe(), keepalive(), timing()

Platform trait updated:
- Renamed read_config → load_firmware (matches actual function)
- Added init() for full x86 dispatch sequence
- Renamed read_sectors → set_read_speed (operation sets speed, not reads)
- Split read_register into read_register_a/b (separate CDBs)

Profile fields used:
- drive_signature, unlock_init_value, unlock_response_size_minus_init
- ld_microcode (1888B firmware payload)
- hardware_register_a_cdb, hardware_register_b_cdb (pre-built CDBs)
- drive_nominal_speed_cdb (calibration triple-play)
- speed_zone_table, speed_calc_table (operation lookups)
2026-04-08 20:11:26 -07:00
MattJackson a4c10887cf Make unlock non-fatal in open_title — BD discs work without it
BD drives reject the MediaTek unlock command (sense 0x05).
Unlock is only needed for UHD raw access. Standard BD reads
work with standard READ(10) without vendor unlock.
2026-04-08 17:30:09 -07:00
MattJackson 3fcab4d8d9 Strip to bare minimum for speed test: no calibration, no maintain_speed
Back to basics: open, unlock, SET CD SPEED max, read.
Remove all calibration probes, register reads, maintain_speed calls.
This is closest to the build that hit 17 MB/s earlier.

Also: drive discovery moved to libfreemkv (find_drive, resolve_device),
AACS via UDF only, clean pipeline, sg device support.
2026-04-08 15:46:42 -07:00
MattJackson f46d9706eb Fix rip: extent LBA offset, u16 truncation, batch reads, read_content
- Fixed: extents were relative to m2ts file, not absolute disc LBAs
- Fixed: u16 truncation of remaining sector count (13M → 36!)
- Added: UdfFs::file_start_lba() for m2ts LBA lookup
- Added: DriveSession::read_content() with 30s timeout for bulk reads
- Added: SET CD SPEED 0xFFFF on title open
- Added: adaptive batch reading (96→48→3 on error, ramp back up)
- Rip working end-to-end: scan → AACS → decrypt → write
2026-04-08 10:12:07 -07:00
MattJackson 9ecbf0ada5 Clean warning, AACS 2.0 full handshake + P-256 2026-04-08 08:27:13 -07:00
MattJackson 82746ed592 AACS 2.0: full P-256 handshake, HC2 parsing, stubbed for credentials
- Full aacs2_authenticate_p256(): AGID → P-256 cert exchange → ECDSA
 signatures → ECDH bus key. Complete SCSI payload format (132-byte
 certs, 128-byte key+sig).
- Falls back: tries AACS 1.0 first, P-256 only if drive rejects v1.
- HC2 KEYDB parsing: | HC2 | HOST_PRIV_KEY 0x... | HOST_CERT 0x...
 (32-byte private key, 132-byte certificate)
- P-256 key pair generation for ephemeral handshake
- AACS 2.0 cert verification with LA public key
- Waiting on: AACS 2.0 host credentials (HC2 entry in KEYDB)
- SKB: detected (use_skb_mkb flag) but not processed — VUK from KEYDB
 still works for all discs

32 tests passing.
2026-04-08 08:03:40 -07:00
MattJackson da0eeae6a7 AACS 2.0: P-256 curve, SHA-256 ECDSA, bus decryption pipeline
- Added P-256/secp256r1 curve constants
- Added AACS 2.0 LA public key for drive cert verification
- Implemented P-256 ECDSA sign/verify (SHA-256)
- Implemented P-256 ECDH for bus key derivation
- Added aacs2_authenticate() entry point (falls back to AACS 1.0)
- Extended HostCert with optional v2 credentials
- Added sha2 dependency
- Added keydb updater (HTTP GET via raw TCP, zip/gzip extraction)
- 4 new P-256 tests: generator, order, ECDSA, ECDH — all passing
- 32 total tests passing
2026-04-07 21:30:09 -07:00
MattJackson 262f9a7f8d Add keydb updater, update README with labels + multi-lingual + real output 2026-04-07 21:21:18 -07:00
MattJackson ca732d57e7 Bump to v0.4.1 2026-04-07 21:01:31 -07:00
MattJackson f357e0fe26 README: update dep version to 0.4, update architecture 2026-04-07 20:57:32 -07:00
MattJackson 976aeb4848 Labels: catch panics — never break disc scan 2026-04-07 20:36:26 -07:00
MattJackson 2bea69c7d9 Release v0.4.0: labels rewrite, eject, capture fix 2026-04-07 20:35:11 -07:00
MattJackson 0bb815ca22 Restructure labels: detect-then-parse, named parsers, raw disc data
Architecture:
- Each BD-J format in own file: paramount.rs, criterion.rs, pixelogic.rs, ctrm.rs
- Standard interface: detect() → bool, parse() → Option<Vec<StreamLabel>>
- PARSERS array in mod.rs — drop in a new parser with one line
- Shared vocab.rs for BD spec codec names only (MLP→TrueHD, AC3→Dolby Digital)
- All other label data passes through raw from disc — no guessing

Changes:
- New: paramount.rs (playlists.xml — Paramount/onQ format)
- Renamed: bluray_project.rs → pixelogic.rs
- Renamed: stream_properties.rs → criterion.rs
- Merged: language_streams.rs + menu_base.rs → ctrm.rs
- Removed: jar module (superseded by labels), dead apply functions
- Added: DriveSession::eject() with PREVENT ALLOW MEDIUM REMOVAL
- Added: DiscRegion enum (Free/BluRay/Dvd)
- Fixed: capture sector ranges now include all files (only skip STREAM/)
- Renamed: StreamLabel.region → variant (not a BD spec field)
2026-04-07 20:29:44 -07:00
MattJackson aa188751e4 Simplify labels API: one call, labels on streams or nothing
labels::apply(session, udf, titles) does everything internally.
Disc::scan() is one line: crate::labels::apply().
No intermediate variables, no fallback logic in disc.rs.

App reads disc.titles[].streams — labels already applied if
disc had config files, empty otherwise. MPLS data always there.
2026-04-07 18:46:29 -07:00
MattJackson ba44f7d928 Add labels module: 4 disc file parsers for stream labels
src/labels/ with 4 parsers tried in order:
1. language_streams.txt (Warner CTRM CSV)
2. menu_base.prop (Warner CTRM properties)
3. streamproperties.xml + playbackconfig.xml (Criterion XML)
4. bluray_project.bin (Pixelogic binary tokens)

Disc::scan() calls labels::extract() → apply_disc_labels().
If no disc files found, streams keep MPLS data as-is.
No JAR fallback — disc files or nothing.

Covers 4/8 discs with JARs (Dunkirk UHD, V for Vendetta BD,
Being There, Barbie). Remaining 3 (Civil War, Dune, V for
Vendetta UHD) have no disc config files.
2026-04-07 18:42:33 -07:00
MattJackson 8a95787426 JAR bytecode tracer POC — extract display names from BD-J enum classes
Traces <clinit> bytecode to find ldc/putstatic pairs that map
enum field names to display name strings. Pattern: new X, dup,
ldc "English", invokespecial X.<init>, putstatic X.a.

Proven on Dune UHD JAR: aw.a="English", aw.b="French", etc.
Next: trace mapping class (ISO code → enum field) to complete the chain.
2026-04-07 16:54:30 -07:00
MattJackson aebe6a256b Fix JAR label matching: by language+codec for label format, by index for TextField
Labels like eng_MLP_ now match to the correct stream by checking language
and codec hint against the stream's properties. Fixes Barbie where labels
were swapped (TrueHD label was on DD stream). TextField format (Civil War)
still uses index matching.

TrackLabel now carries language + codec_hint for structured matching.
2026-04-07 16:27:47 -07:00
MattJackson 791b832f25 Rewrite JAR parser: multi-format with TextField support
- Extract all class strings once, try format parsers in chain
- Format 1: TextField,Audio{N} (A24/Lionsgate) — fixes Civil War
- Format 2: eng_MLP_ label strings (Warner UHD) — Barbie, Dune
- Format 3: playlist-only (MAIN_FEATURE etc.)
- Simplified TrackLabel to just description + raw
- 5 unit tests for both formats
- Tested on 12 disc captures: Civil War now gets 3 audio + 2 sub labels

Known issues: Barbie labels swapped, Dune returns 0 labels (different format?)
2026-04-07 16:23:14 -07:00
MattJackson 580ce2277d Add serial_number to DriveId from GET_CONFIG 0x0108 2026-04-07 16:14:55 -07:00
MattJackson 8a556c5c63 Merge JAR labels into streams during Disc::scan()
apply_jar_labels() matches JAR audio/subtitle labels to streams by
position and sets AudioStream.label. Apps read labels directly from
streams instead of doing their own JAR matching.
2026-04-07 16:10:43 -07:00
MattJackson 520a3f912c Refactor Stream to enum with typed variants (Video, Audio, Subtitle)
Each stream type has only its relevant fields. No more HDR on audio
or channels on video. Added SubtitleStream.forced field (TODO: parse).
Removed display helpers from lib (belongs in CLI).
2026-04-07 16:08:45 -07:00
MattJackson 6f0eea48fa Add Clip struct to Title — expose clip references for playlist analysis
Title.clips[] contains clip_id, in/out times, duration, source_packets.
Apps can detect fake/scrambled playlists by checking unique clip count
vs total (253 clips referencing 2 unique = fake). Removed clip_count
field (use clips.len()).
2026-04-07 16:03:56 -07:00
MattJackson b01010f2d1 Fix title sizes: use pkt_count*192, fix metadata partition range
- Size from source_packet_count * 192 (matches libbluray approach)
- metadata_sector_ranges() uses actual metadata partition size from ICB
 instead of arbitrary +256 margin (fixes Top Gun missing CLIPINF)
- Store metadata_sectors in UdfFs
- Fix CLPI CPI bit-packed field parsing (partial, EP map still needs work)
2026-04-07 15:49:53 -07:00
MattJackson fd443e3a2f Add disc title, format, streams to Disc::scan() — move logic from CLI to lib
- Disc.volume_id: UDF Volume ID from PVD (always present)
- Disc.meta_title: from META/DL/bdmt_eng.xml (falls back to other languages)
- Disc.format: UHD/BluRay/DVD detected from video codec
- Disc.capacity_bytes, Disc.layers
- Disc.jar_labels: extracted from BDMV/JAR
- Fixed MPLS STN parsing: 16-byte header (was 8), proper stream entry offsets
- Streams now include: HDR, color space, Dolby Vision EL, secondary audio/video
- parse_dstring() for UDF d-string fields
- wait_ready() polls TEST UNIT READY before unlock

Tested on 12 disc captures — all return correct titles, streams, format.
2026-04-07 15:36:05 -07:00
MattJackson 8d85a9d778 Add metadata_sector_ranges() and max_metadata_sector() to UdfFs
Walk UDF tree to discover sector ranges for all metadata files
(MPLS, CLPI, JAR, AACS certs, etc). Skips STREAM/, BACKUP/,
DUPLICATE/, MKB_RO.inf, ContentHash. Exposes partition_start()
and metadata_start() getters. Used by bdemu smart capture.
2026-04-07 14:43:42 -07:00
MattJackson 237528b385 Add macOS SCSI support via IOKit SCSITaskDeviceInterface
IOKit backend for macOS optical drives. Accepts BSD device paths
(/dev/disk2), walks IORegistry to find authoring device, sends
SCSI commands through SCSITaskDeviceInterface COM vtable.
2026-04-07 13:15:06 -07:00
MattJackson 492a2966c0 Add docs/README.md — reading order and index for all documentation
Start with disc-to-rip.md (the big picture), then architecture,
drive access, disc formats (UDF/MPLS/CLPI), and AACS encryption.
2026-04-07 12:36:29 -07:00
MattJackson 5fa6b064da Remove standalone binaries — functionality lives in freemkv CLI and tests
Deleted:
- freemkv_info.rs — duplicate of freemkv drive-info CLI command
- freemkv_test.rs — duplicate of bdemu capture-disc
- aacs_test.rs — covered by inline #[test] functions (31 tests)

libfreemkv is a library. CLI tools belong in the freemkv repo.
Dev/debug tools belong in (internal).
2026-04-07 12:34:10 -07:00
MattJackson df652c5735 Restructure: scsi/ and aacs/ as module directories
- scsi.rs → scsi/mod.rs (trait, constants, CDB builders) + scsi/linux.rs (SG_IO)
 Ready for scsi/macos.rs and scsi/windows.rs when needed.
- aacs.rs + aacs_handshake.rs → aacs/mod.rs (keys, decrypt) + aacs/handshake.rs (SCSI auth)
- All internal refs use super:: within module, crate:: across modules
- Zero warnings, 31 tests passing
2026-04-07 12:31:05 -07:00
MattJackson d0c5c7fb97 Named constants, error codes, SCSI opcodes, flow documentation
- Error codes as public constants (E_DEVICE_NOT_FOUND etc.) — single source of truth
- SCSI opcodes as named constants (SCSI_READ_10, SCSI_REPORT_KEY, etc.)
- AACS key class constant, MKB format constant
- No magic numbers in CDB construction
- docs/disc-to-rip.md — end-to-end flow from disc insert to decrypted content
- Links all module docs together as a starting point
2026-04-07 12:26:30 -07:00
MattJackson 6ab355245f Update README: remove hard numbers, add AACS and disc scanning
- Removed specific drive counts (was "206 bundled")
- Removed version-specific install instructions
- Added Disc::scan() + ContentReader as primary API example
- Added AACS section explaining transparent decryption
- Added docs/ link for technical documentation
- Added E7xxx AACS error range
- Evergreen — no dates or counts that need manual maintenance
2026-04-07 12:21:08 -07:00
MattJackson 272215c551 Clean up for public release: docs, zero warnings, no hardcoded paths
Documentation:
- docs/aacs.md — AACS encryption (1.0 + 2.0), key resolution, decrypt
- docs/udf.md — UDF 2.50 filesystem with metadata partitions
- docs/mpls.md — MPLS playlist format, STN stream table
- docs/clpi.md — CLPI clip info, EP map, sector extents
- docs/architecture.md — library module map, design principles
- docs/drive-access.md — drive sessions, SCSI transport, unlock

Code cleanup:
- Zero compiler warnings
- Removed all debug eprintln from library code
- No hardcoded private paths — KEYDB tests use KEYDB_PATH env var
- KEYDB search locations as named constants
- drive.rs: extracted create_platform(), deduplicated open methods
- lib.rs: updated doc examples to show Disc::scan() API
- Fixed UDF file reads (partition_start, not metadata_start)
- Exported KeySource from disc module
2026-04-07 12:12:24 -07:00
MattJackson 721308cb8e Fix UDF file data reads: use partition_start not metadata_start
File data extents are in the physical partition (partition_start + lba),
not the metadata partition. ICBs (directory/file entries) are in metadata,
but the data they point to is in physical. This one-line fix makes all
MPLS playlists readable — previously only 1/18 had correct MPLS magic.

Tested on Civil War UHD: 18 playlists, main movie at 00400.mpls (57min, 6 clips).
2026-04-07 11:44:23 -07:00
MattJackson cc58ab1754 AACS graceful fallback: handshake optional, disc-hash-only KEYDB path
- SCSI handshake now optional: if drive doesn't support AACS layer
 (e.g. raw-mode drives where AACS feature current=0), falls back
 to disc-hash-only KEYDB lookup
- open_no_unlock(): open drive without entering raw mode
- device_path tracking on DriveSession
- Real drive test: BU40N with Civil War UHD
 - Handshake correctly fails (05/6F/01 key not present — expected)
 - Scan continues, finds AACS directory, reads Unit_Key_RO.inf
 - KEYDB lookup attempted (disc hash matching WIP)
2026-04-07 11:41:34 -07:00
MattJackson 193b571967 Expose full AACS state: version, key source, disc hash, all keys public
- AacsState now exposes: version (1/2), bus_encryption, mkb_version,
 disc_hash, key_source (KeyDb/KeyDbDerived/ProcessingKey/DeviceKey),
 vuk, unit_keys, read_data_key, volume_id
- All keys are public — apps that contribute to KEYDB ecosystem can
 read VUK, unit keys, Volume ID, disc hash for database submission
- resolve_keys reports which path succeeded (key_source 1-4)
- Cleaned up resolve_keys with shared builder to reduce duplication
2026-04-07 11:25:04 -07:00
MattJackson bac2e39ee5 AACS 100%: device key tree, MKB SCSI read, AACS2 detection, STN streams
- Device key subset-difference tree: aesg3 key derivation, v_mask calc,
 tree traversal from device key to processing key to media key
- MKB SCSI read: REPORT DISC STRUCTURE 0x83 with multi-pack support
- resolve_keys now has 4 paths:
 1. disc hash → KEYDB → VUK
 2. KEYDB media key + VID → VUK
 3. MKB + processing keys → media key → VUK
 4. MKB + device keys → processing key → media key → VUK
- setup_aacs reads MKB from drive (not just from file)
- AACS 2.0 detection: drive cert type 0x11 detected, falls back to
 AACS 1.0 handshake (P-256 crypto path prepared but not yet built)
- STN table parsing in mpls.rs: video format/rate, audio format/rate/lang,
 subtitle lang, coding type — all streamed into Disc title streams
- 31 tests passing
2026-04-07 11:19:34 -07:00
MattJackson 9825db82aa AACS complete: Unit_Key_RO.inf parser, disc hash, MKB processing, resolve chain
- Unit_Key_RO.inf: proper parser matching libaacs format (uk_pos, stride,
 AACS1 48-byte / AACS2 64-byte key spacing, title→CPS unit mapping)
- Disc hash: SHA1 of Unit_Key_RO.inf for KEYDB lookup
- MKB processing: processing keys + subdiff records + cvalues → media key
 (verify media key record with 12-byte zero check)
- Content Certificate parser: detect AACS version + bus encryption flag
- resolve_keys(): full 3-path chain:
 1. disc hash → KEYDB → VUK (fast, 99% of discs)
 2. KEYDB media key + VID → VUK
 3. MKB + processing keys → media key → VUK
- setup_aacs() now reads Unit_Key_RO.inf + Content Cert from disc via UDF
- 31 tests passing
2026-04-07 11:13:20 -07:00
MattJackson 985d00f0a4 AACS 2.0 full pipeline: handshake, bus key, decrypt, transparent API
- aacs.rs: content decryption (AES-CBC aligned units, bus decrypt, VUK
 derivation, unit key decrypt). 11 tests including synthetic roundtrip.
- aacs_handshake.rs: SCSI authentication (ECDH on AACS 160-bit curve,
 ECDSA sign/verify, AES-CMAC, bus key derivation, read_data_key).
 14 tests including EC order, ECDH shared secret, cert verification.
- disc.rs: transparent API — Disc::scan() detects AACS, authenticates,
 derives keys internally. ContentReader decrypts on the fly. App never
 sees AACS details.
- error.rs: AacsError (E7000) variant

BF v12 complete: 284 unique matches from 8 AWS instances.
2026-04-06 20:40:52 -07:00
MattJackson dbc5789406 Add AACS module: KEYDB.cfg parser, VUK lookup, 180K disc entries in <2s
Parses device keys, processing keys, host cert, per-disc VUKs.
Full KEYDB.cfg (60MB, 180K entries) parsed in 1.85 seconds.
All test discs found with VUKs.

Next: disc hash computation, title key decryption, content decryption.
2026-04-06 18:49:58 -07:00
MattJackson eb3b84216a Reduce read_disc timeout to 5s (was 30s)
Prevents indefinite hangs on encrypted/unreadable sectors.
Some discs put MPLS/CLPI/JAR data in AACS-encrypted regions
that block reads without proper AACS authentication.
2026-04-06 18:22:43 -07:00
MattJackson a07a7aebf2 Always unlock on DriveSession::open()
Silently ignores unlock failures (unencrypted discs).
Fixes disc-info on discs that gate reads behind AACS auth.
2026-04-06 17:06:32 -07:00
MattJackson 37ee573014 Add JAR parser: extract audio/subtitle labels from BD-J menus
Scans BDMV/JAR/*.jar class files for track label patterns:
 eng_MLP_ → TrueHD, eng_ADES_US_ → Descriptive Audio (US)
 dan_PGStream4 → Danish subtitles

Solves the "3x AC-3 English" problem — we identify Descriptive Audio, commentary, compatibility tracks.

Future: set MKV "visual impaired" flag automatically during mux.

Local only — needs more disc samples to validate before publishing.
2026-04-06 16:09:30 -07:00
MattJackson b34b7dd2e6 Improve API docs, add docs.rs badge + link 2026-04-06 15:58:32 -07:00
MattJackson dd0f49fa3d v0.3.0: disc format parsers (UDF 2.50, MPLS, CLPI), typed stream API 2026-04-06 15:50:56 -07:00
MattJackson 7d3284cf28 UDF: file data on physical partition, disc API with typed streams 2026-04-06 15:50:41 -07:00
MattJackson 117a7823d6 Library disc API: Disc, Title, Stream with typed codec/hdr/color
Clean public API for disc scanning. CLI should only display,
never parse binary formats directly.

Disc::scan() → titles → streams with:
 codec (Hevc/TrueHd/Ac3/Pgs/etc), pid, language,
 resolution, frame_rate, channels, hdr, color_space,
 secondary flag, label
2026-04-06 15:47:07 -07:00
MattJackson 7db4606038 Add disc format parsers: UDF, MPLS, CLPI
- udf.rs: read files from Blu-ray disc filesystem
- mpls.rs: parse playlists → titles with clips and timestamps
- clpi.rs: parse clip info → EP map with coarse/fine SPN entries
- disc.rs: high-level title scanning, sector extent mapping
- drive.rs: add read_disc() for unencrypted reads, scsi_execute()
- error.rs: add E6000 DiscError

Stage 1 of freemkv rip: identify titles and their sector ranges.
2026-04-06 14:27:48 -07:00
MattJackson 3f083f57de Fix SG_IO type for musl compatibility 2026-04-06 12:55:30 -07:00
MattJackson 326edc4e83 Auto-update README on release 2026-04-06 12:50:47 -07:00
MattJackson 1e90497760 README: consistent layout, remove prebuilt binary section (library only) 2026-04-06 12:33:20 -07:00
MattJackson 509cdf6ea7 v0.2.1: update crates.io README, version check in release CI 2026-04-06 12:19:39 -07:00
MattJackson ee230fe4fb Release: verify Cargo.toml version matches git tag 2026-04-06 12:13:36 -07:00
MattJackson ff46200829 CI: test on push/PR, auto-publish to crates.io on tag
- ci.yml: test on ubuntu + check on macos for every push/PR
- release.yml: test → publish to crates.io → create GitHub Release
2026-04-06 11:48:20 -07:00
MattJackson 55bc96eeee Add release workflow + prebuilt binary downloads in README
- GitHub Actions: build Linux x86_64 + aarch64 on tag push
- Upload tar.gz binaries to GitHub Releases
- README: download table linking to releases
2026-04-06 11:44:46 -07:00
MattJackson 3a0ea29cf7 v0.2.0: Chipset architecture, platform-agnostic SCSI, no gatekeeping
Breaking changes from v0.1.2:
- PlatformType enum removed, replaced by Chipset (MediaTek, Renesas)
- DriveProfile.platform removed, replaced by .chipset + .unlock_mode + .unlock_buf_id
- DriveProfile.supported field removed — no gatekeeping
- ReadinessStatus enum removed
- DriveMatch enum removed
- SgIoTransport::open() replaced by scsi::open() (platform-agnostic)
- profiles.json: "program" field replaced by "chipset" + "unlock_mode" + "unlock_buf_id"

New:
- scsi::open() with cfg gates for Linux/macOS/Windows
- Compiles on macOS (stub returns platform-not-supported)
- libc dependency conditional on Linux only
2026-04-06 11:41:26 -07:00
MattJackson e094fc66ba Platform-agnostic SCSI transport with cfg gates
- scsi::open() returns Box<dyn ScsiTransport> on any platform
- Linux SG_IO behind #[cfg(target_os = "linux")]
- macOS IOKit and Windows SPTI stubs with TODO
- drive.rs uses scsi::open() — no platform-specific imports
- Compiles on macOS (returns "platform not yet supported")
2026-04-06 11:34:33 -07:00
MattJackson 0b2e390715 Remove supported/status gatekeeping, update README
- Removed supported field — if we have a profile, try unlock
- Removed ReadinessStatus, DriveMatch, flashable detection
- N0 and NM firmware both unlockable (library is agnostic)
- README: full API docs, chipset table, error codes, example output
2026-04-06 11:29:57 -07:00
MattJackson c26b6f6819 Refactor: Chipset architecture, remove supported/status gatekeeping
- PlatformType → Chipset enum (MediaTek, Renesas)
- unlock_mode + unlock_buf_id stored in profile, not derived from enum
- Removed ReadinessStatus, supported field, needs_flash — library is agnostic
- Removed DriveMatch/Flashable — if we have a profile, try unlock
- profiles.json: chipset + unlock_mode + unlock_buf_id, no program/supported
- mt1959.rs reads mode/buf_id from profile fields directly
- Tests: find_known_drive, find_unknown_drive
2026-04-06 11:21:27 -07:00
MattJackson afb43c69b3 Add CONTRIBUTING.md, issue templates, badges 2026-04-06 10:43:52 -07:00
MattJackson ddcb614777 v0.1.2: 206 supported drives (was 141)
Fixed firmware database unpacker offset bug — recovered 65 additional drives.
All code.files verified: identical code (676 bytes), variable data.
Signature at offset 0x35C confirmed correct for all 8 code sizes.

 mt1959_a (mode=1, buf=0x44): 65 drives
 mt1959_b (mode=2, buf=0x77): 141 drives
2026-04-06 10:42:12 -07:00
MattJackson cbf510e70e Structured error codes: E1000-E5000, no user-facing text in library
Error codes:
 1000-1001: device errors (not found, permission)
 2000-2002: profile errors (unsupported, not found, parse)
 3000-3003: unlock errors (failed, signature, not unlocked, not calibrated)
 4000-4001: SCSI errors (command failed, timeout)
 5000: I/O errors

All errors carry structured data (vendor_id, opcode, etc).
Applications format their own user-facing messages.
Library returns code + data, never English text.
2026-04-06 10:17:43 -07:00
MattJackson 4f0303dcc9 v0.1.1: clean profiles, SPC-4/MMC-6 field names 2026-04-06 10:00:13 -07:00