Commit Graph
453 Commits
Author SHA1 Message Date
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