decrypt_sectors returns Result — fail instead of silent corruption
Previously used all-zeros AES key when unit_key_idx was out of range, producing silently corrupted output. Now returns DecryptFailed error. Also wired --raw flag through InputOptions → set_raw() on streams.
This commit is contained in:
+27
-9
@@ -10,6 +10,7 @@ use crate::css;
|
|||||||
/// Resolved decryption state from disc scanning.
|
/// Resolved decryption state from disc scanning.
|
||||||
/// Passed to `decrypt_sectors()` — the caller doesn't need to know
|
/// Passed to `decrypt_sectors()` — the caller doesn't need to know
|
||||||
/// which encryption scheme is in use.
|
/// which encryption scheme is in use.
|
||||||
|
#[derive(Clone)]
|
||||||
pub enum DecryptKeys {
|
pub enum DecryptKeys {
|
||||||
/// No encryption on this disc.
|
/// No encryption on this disc.
|
||||||
None,
|
None,
|
||||||
@@ -19,9 +20,7 @@ pub enum DecryptKeys {
|
|||||||
read_data_key: Option<[u8; 16]>,
|
read_data_key: Option<[u8; 16]>,
|
||||||
},
|
},
|
||||||
/// CSS (DVD). Title key for sector descrambling.
|
/// CSS (DVD). Title key for sector descrambling.
|
||||||
Css {
|
Css { title_key: [u8; 5] },
|
||||||
title_key: [u8; 5],
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl DecryptKeys {
|
impl DecryptKeys {
|
||||||
@@ -38,14 +37,32 @@ impl DecryptKeys {
|
|||||||
/// For None: no-op.
|
/// For None: no-op.
|
||||||
///
|
///
|
||||||
/// `unit_key_idx` selects which AACS unit key to use (0 for most discs).
|
/// `unit_key_idx` selects which AACS unit key to use (0 for most discs).
|
||||||
pub fn decrypt_sectors(buf: &mut [u8], keys: &DecryptKeys, unit_key_idx: usize) {
|
///
|
||||||
|
/// Returns `Err` if decryption was expected but keys are missing or invalid.
|
||||||
|
/// Never produces silently corrupted output.
|
||||||
|
pub fn decrypt_sectors(
|
||||||
|
buf: &mut [u8],
|
||||||
|
keys: &DecryptKeys,
|
||||||
|
unit_key_idx: usize,
|
||||||
|
) -> Result<(), crate::error::Error> {
|
||||||
match keys {
|
match keys {
|
||||||
DecryptKeys::None => {}
|
DecryptKeys::None => {}
|
||||||
DecryptKeys::Aacs { unit_keys, read_data_key } => {
|
DecryptKeys::Aacs {
|
||||||
let uk = unit_keys
|
unit_keys,
|
||||||
.get(unit_key_idx)
|
read_data_key,
|
||||||
.map(|(_, k)| *k)
|
} => {
|
||||||
.unwrap_or([0u8; 16]);
|
let uk = match unit_keys.get(unit_key_idx) {
|
||||||
|
Some((_, k)) => *k,
|
||||||
|
None => {
|
||||||
|
return Err(crate::error::Error::DecryptFailed {
|
||||||
|
reason: format!(
|
||||||
|
"unit_key_idx {} out of range (have {} keys)",
|
||||||
|
unit_key_idx,
|
||||||
|
unit_keys.len()
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
let rdk = read_data_key.as_ref();
|
let rdk = read_data_key.as_ref();
|
||||||
let unit_len = aacs::ALIGNED_UNIT_LEN;
|
let unit_len = aacs::ALIGNED_UNIT_LEN;
|
||||||
|
|
||||||
@@ -61,4 +78,5 @@ pub fn decrypt_sectors(buf: &mut [u8], keys: &DecryptKeys, unit_key_idx: usize)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
+511
-86
@@ -148,10 +148,10 @@ pub struct VideoStream {
|
|||||||
pub pid: u16,
|
pub pid: u16,
|
||||||
/// Codec (HEVC, H.264, VC-1, MPEG-2)
|
/// Codec (HEVC, H.264, VC-1, MPEG-2)
|
||||||
pub codec: Codec,
|
pub codec: Codec,
|
||||||
/// Resolution (e.g. "2160p", "1080p", "1080i")
|
/// Resolution
|
||||||
pub resolution: String,
|
pub resolution: Resolution,
|
||||||
/// Frame rate (e.g. "23.976", "25")
|
/// Frame rate
|
||||||
pub frame_rate: String,
|
pub frame_rate: FrameRate,
|
||||||
/// HDR format
|
/// HDR format
|
||||||
pub hdr: HdrFormat,
|
pub hdr: HdrFormat,
|
||||||
/// Color space
|
/// Color space
|
||||||
@@ -169,12 +169,12 @@ pub struct AudioStream {
|
|||||||
pub pid: u16,
|
pub pid: u16,
|
||||||
/// Codec (TrueHD, DTS-HD MA, DD, LPCM, etc.)
|
/// Codec (TrueHD, DTS-HD MA, DD, LPCM, etc.)
|
||||||
pub codec: Codec,
|
pub codec: Codec,
|
||||||
/// Channel layout (e.g. "5.1", "7.1", "stereo", "mono")
|
/// Channel layout
|
||||||
pub channels: String,
|
pub channels: AudioChannels,
|
||||||
/// ISO 639-2 language code (e.g. "eng", "fra")
|
/// ISO 639-2 language code (e.g. "eng", "fra")
|
||||||
pub language: String,
|
pub language: String,
|
||||||
/// Sample rate (e.g. "48kHz", "96kHz")
|
/// Sample rate
|
||||||
pub sample_rate: String,
|
pub sample_rate: SampleRate,
|
||||||
/// Whether this is a secondary stream (commentary)
|
/// Whether this is a secondary stream (commentary)
|
||||||
pub secondary: bool,
|
pub secondary: bool,
|
||||||
/// Extra label
|
/// Extra label
|
||||||
@@ -204,6 +204,8 @@ pub enum Codec {
|
|||||||
H264,
|
H264,
|
||||||
Vc1,
|
Vc1,
|
||||||
Mpeg2,
|
Mpeg2,
|
||||||
|
Mpeg1,
|
||||||
|
Av1,
|
||||||
// Audio
|
// Audio
|
||||||
TrueHd,
|
TrueHd,
|
||||||
DtsHdMa,
|
DtsHdMa,
|
||||||
@@ -212,19 +214,118 @@ pub enum Codec {
|
|||||||
Ac3,
|
Ac3,
|
||||||
Ac3Plus,
|
Ac3Plus,
|
||||||
Lpcm,
|
Lpcm,
|
||||||
|
Aac,
|
||||||
|
Mp2,
|
||||||
|
Mp3,
|
||||||
|
Flac,
|
||||||
|
Opus,
|
||||||
// Subtitle
|
// Subtitle
|
||||||
Pgs,
|
Pgs,
|
||||||
DvdSub,
|
DvdSub,
|
||||||
|
Srt,
|
||||||
|
Ssa,
|
||||||
// Unknown
|
// Unknown
|
||||||
Unknown(u8),
|
Unknown(u8),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Video resolution.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum Resolution {
|
||||||
|
/// 480i (720x480 interlaced) — NTSC DVD
|
||||||
|
R480i,
|
||||||
|
/// 480p (720x480 progressive)
|
||||||
|
R480p,
|
||||||
|
/// 576i (720x576 interlaced) — PAL DVD
|
||||||
|
R576i,
|
||||||
|
/// 576p (720x576 progressive)
|
||||||
|
R576p,
|
||||||
|
/// 720p (1280x720 progressive) — some Blu-rays
|
||||||
|
R720p,
|
||||||
|
/// 1080i (1920x1080 interlaced) — broadcast, some BD
|
||||||
|
R1080i,
|
||||||
|
/// 1080p (1920x1080 progressive) — standard Blu-ray
|
||||||
|
R1080p,
|
||||||
|
/// 2160p (3840x2160 progressive) — 4K UHD Blu-ray
|
||||||
|
R2160p,
|
||||||
|
/// 4320p (7680x4320 progressive) — 8K, future-proof
|
||||||
|
R4320p,
|
||||||
|
/// Unknown resolution
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Video frame rate.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
pub enum FrameRate {
|
||||||
|
/// 23.976 fps — film-based BD/UHD (NTSC pulldown)
|
||||||
|
F23_976,
|
||||||
|
/// 24.000 fps — true film rate
|
||||||
|
F24,
|
||||||
|
/// 25.000 fps — PAL standard
|
||||||
|
F25,
|
||||||
|
/// 29.970 fps — NTSC standard
|
||||||
|
F29_97,
|
||||||
|
/// 30.000 fps
|
||||||
|
F30,
|
||||||
|
/// 50.000 fps — PAL high frame rate
|
||||||
|
F50,
|
||||||
|
/// 59.940 fps — NTSC high frame rate
|
||||||
|
F59_94,
|
||||||
|
/// 60.000 fps
|
||||||
|
F60,
|
||||||
|
/// Unknown frame rate
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio channel layout.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum AudioChannels {
|
||||||
|
/// 1.0 mono
|
||||||
|
Mono,
|
||||||
|
/// 2.0 stereo
|
||||||
|
Stereo,
|
||||||
|
/// 2.1 (stereo + LFE)
|
||||||
|
Stereo21,
|
||||||
|
/// 4.0 quadraphonic
|
||||||
|
Quad,
|
||||||
|
/// 5.0 surround (no LFE)
|
||||||
|
Surround50,
|
||||||
|
/// 5.1 surround — standard BD/DVD surround
|
||||||
|
Surround51,
|
||||||
|
/// 6.1 surround (DTS-ES, Dolby EX)
|
||||||
|
Surround61,
|
||||||
|
/// 7.1 surround — UHD Atmos beds, DTS:X
|
||||||
|
Surround71,
|
||||||
|
/// Unknown channel layout
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Audio sample rate.
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub enum SampleRate {
|
||||||
|
/// 44.1 kHz — CD audio (rare on disc)
|
||||||
|
S44_1,
|
||||||
|
/// 48 kHz — standard BD/DVD/UHD audio
|
||||||
|
S48,
|
||||||
|
/// 96 kHz — high-res BD audio
|
||||||
|
S96,
|
||||||
|
/// 192 kHz — highest BD audio (LPCM)
|
||||||
|
S192,
|
||||||
|
/// 48/96 kHz combo (secondary audio resampled)
|
||||||
|
S48_96,
|
||||||
|
/// 48/192 kHz combo (secondary audio resampled)
|
||||||
|
S48_192,
|
||||||
|
/// Unknown sample rate
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
/// HDR format.
|
/// HDR format.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum HdrFormat {
|
pub enum HdrFormat {
|
||||||
Sdr,
|
Sdr,
|
||||||
Hdr10,
|
Hdr10,
|
||||||
|
Hdr10Plus,
|
||||||
DolbyVision,
|
DolbyVision,
|
||||||
|
Hlg,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Color space.
|
/// Color space.
|
||||||
@@ -254,24 +355,50 @@ pub struct Extent {
|
|||||||
// ─── Display helpers ────────────────────────────────────────────────────────
|
// ─── Display helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
impl Codec {
|
impl Codec {
|
||||||
|
/// Human-readable display name.
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
for (_, name, v) in Self::ALL_CODECS {
|
||||||
Codec::Hevc => "HEVC",
|
if v == self {
|
||||||
Codec::H264 => "H.264",
|
return name;
|
||||||
Codec::Vc1 => "VC-1",
|
|
||||||
Codec::Mpeg2 => "MPEG-2",
|
|
||||||
Codec::TrueHd => "TrueHD",
|
|
||||||
Codec::DtsHdMa => "DTS-HD MA",
|
|
||||||
Codec::DtsHdHr => "DTS-HD HR",
|
|
||||||
Codec::Dts => "DTS",
|
|
||||||
Codec::Ac3 => "AC-3",
|
|
||||||
Codec::Ac3Plus => "AC-3+",
|
|
||||||
Codec::Lpcm => "LPCM",
|
|
||||||
Codec::Pgs => "PGS",
|
|
||||||
Codec::DvdSub => "DVD Subtitle",
|
|
||||||
Codec::Unknown(_) => "Unknown",
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"Unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Compact identifier for serialization (lowercase, no spaces).
|
||||||
|
pub fn id(&self) -> &'static str {
|
||||||
|
for (id, _, v) in Self::ALL_CODECS {
|
||||||
|
if v == self {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"unknown"
|
||||||
|
}
|
||||||
|
|
||||||
|
const ALL_CODECS: &[(&'static str, &'static str, Codec)] = &[
|
||||||
|
("hevc", "HEVC", Codec::Hevc),
|
||||||
|
("h264", "H.264", Codec::H264),
|
||||||
|
("vc1", "VC-1", Codec::Vc1),
|
||||||
|
("mpeg2", "MPEG-2", Codec::Mpeg2),
|
||||||
|
("mpeg1", "MPEG-1", Codec::Mpeg1),
|
||||||
|
("av1", "AV1", Codec::Av1),
|
||||||
|
("truehd", "TrueHD", Codec::TrueHd),
|
||||||
|
("dtshd_ma", "DTS-HD MA", Codec::DtsHdMa),
|
||||||
|
("dtshd_hr", "DTS-HD HR", Codec::DtsHdHr),
|
||||||
|
("dts", "DTS", Codec::Dts),
|
||||||
|
("ac3", "AC-3", Codec::Ac3),
|
||||||
|
("eac3", "EAC-3", Codec::Ac3Plus),
|
||||||
|
("lpcm", "LPCM", Codec::Lpcm),
|
||||||
|
("aac", "AAC", Codec::Aac),
|
||||||
|
("mp2", "MP2", Codec::Mp2),
|
||||||
|
("mp3", "MP3", Codec::Mp3),
|
||||||
|
("flac", "FLAC", Codec::Flac),
|
||||||
|
("opus", "Opus", Codec::Opus),
|
||||||
|
("pgs", "PGS", Codec::Pgs),
|
||||||
|
("dvdsub", "DVD Subtitle", Codec::DvdSub),
|
||||||
|
("srt", "SRT", Codec::Srt),
|
||||||
|
("ssa", "SSA", Codec::Ssa),
|
||||||
|
];
|
||||||
|
|
||||||
fn from_coding_type(ct: u8) -> Self {
|
fn from_coding_type(ct: u8) -> Self {
|
||||||
match ct {
|
match ct {
|
||||||
@@ -293,14 +420,233 @@ impl Codec {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for Codec {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resolution {
|
||||||
|
/// Parse from MPLS video_format byte.
|
||||||
|
pub fn from_video_format(vf: u8) -> Self {
|
||||||
|
match vf {
|
||||||
|
1 => Resolution::R480i,
|
||||||
|
2 => Resolution::R576i,
|
||||||
|
3 => Resolution::R480p,
|
||||||
|
4 => Resolution::R1080i,
|
||||||
|
5 => Resolution::R720p,
|
||||||
|
6 => Resolution::R1080p,
|
||||||
|
7 => Resolution::R576p,
|
||||||
|
8 => Resolution::R2160p,
|
||||||
|
_ => Resolution::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Pixel dimensions (width, height).
|
||||||
|
pub fn pixels(&self) -> (u32, u32) {
|
||||||
|
match self {
|
||||||
|
Resolution::R480i | Resolution::R480p => (720, 480),
|
||||||
|
Resolution::R576i | Resolution::R576p => (720, 576),
|
||||||
|
Resolution::R720p => (1280, 720),
|
||||||
|
Resolution::R1080i | Resolution::R1080p => (1920, 1080),
|
||||||
|
Resolution::R2160p => (3840, 2160),
|
||||||
|
Resolution::R4320p => (7680, 4320),
|
||||||
|
Resolution::Unknown => (1920, 1080),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this is a UHD (4K+) resolution.
|
||||||
|
pub fn is_uhd(&self) -> bool {
|
||||||
|
matches!(self, Resolution::R2160p | Resolution::R4320p)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this is an HD (720p+) resolution.
|
||||||
|
pub fn is_hd(&self) -> bool {
|
||||||
|
!matches!(
|
||||||
|
self,
|
||||||
|
Resolution::R480i
|
||||||
|
| Resolution::R480p
|
||||||
|
| Resolution::R576i
|
||||||
|
| Resolution::R576p
|
||||||
|
| Resolution::Unknown
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// True if this is an SD (480/576) resolution.
|
||||||
|
pub fn is_sd(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self,
|
||||||
|
Resolution::R480i | Resolution::R480p | Resolution::R576i | Resolution::R576p
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from pixel height (e.g. from MKV track).
|
||||||
|
pub fn from_height(h: u32) -> Self {
|
||||||
|
match h {
|
||||||
|
0..=480 => Resolution::R480p,
|
||||||
|
481..=576 => Resolution::R576p,
|
||||||
|
577..=720 => Resolution::R720p,
|
||||||
|
721..=1080 => Resolution::R1080p,
|
||||||
|
1081..=2160 => Resolution::R2160p,
|
||||||
|
_ => Resolution::R4320p,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display for Resolution is generated by enum_str! macro
|
||||||
|
|
||||||
|
impl FrameRate {
|
||||||
|
/// Parse from MPLS video_rate byte.
|
||||||
|
pub fn from_video_rate(vr: u8) -> Self {
|
||||||
|
match vr {
|
||||||
|
1 => FrameRate::F23_976,
|
||||||
|
2 => FrameRate::F24,
|
||||||
|
3 => FrameRate::F25,
|
||||||
|
4 => FrameRate::F29_97,
|
||||||
|
5 => FrameRate::F30,
|
||||||
|
6 => FrameRate::F50,
|
||||||
|
7 => FrameRate::F59_94,
|
||||||
|
8 => FrameRate::F60,
|
||||||
|
_ => FrameRate::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Frame rate as (numerator, denominator) for precise representation.
|
||||||
|
pub fn as_fraction(&self) -> (u32, u32) {
|
||||||
|
match self {
|
||||||
|
FrameRate::F23_976 => (24000, 1001),
|
||||||
|
FrameRate::F24 => (24, 1),
|
||||||
|
FrameRate::F25 => (25, 1),
|
||||||
|
FrameRate::F29_97 => (30000, 1001),
|
||||||
|
FrameRate::F30 => (30, 1),
|
||||||
|
FrameRate::F50 => (50, 1),
|
||||||
|
FrameRate::F59_94 => (60000, 1001),
|
||||||
|
FrameRate::F60 => (60, 1),
|
||||||
|
FrameRate::Unknown => (0, 1),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display for FrameRate is generated by enum_str! macro
|
||||||
|
|
||||||
|
impl AudioChannels {
|
||||||
|
/// Parse from MPLS audio_format byte.
|
||||||
|
pub fn from_audio_format(af: u8) -> Self {
|
||||||
|
match af {
|
||||||
|
1 => AudioChannels::Mono,
|
||||||
|
3 => AudioChannels::Stereo,
|
||||||
|
6 => AudioChannels::Surround51,
|
||||||
|
12 => AudioChannels::Surround71,
|
||||||
|
_ if af > 0 => AudioChannels::Unknown,
|
||||||
|
_ => AudioChannels::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Channel count as a number.
|
||||||
|
pub fn count(&self) -> u8 {
|
||||||
|
match self {
|
||||||
|
AudioChannels::Mono => 1,
|
||||||
|
AudioChannels::Stereo => 2,
|
||||||
|
AudioChannels::Stereo21 => 3,
|
||||||
|
AudioChannels::Quad => 4,
|
||||||
|
AudioChannels::Surround50 => 5,
|
||||||
|
AudioChannels::Surround51 => 6,
|
||||||
|
AudioChannels::Surround61 => 7,
|
||||||
|
AudioChannels::Surround71 => 8,
|
||||||
|
AudioChannels::Unknown => 6,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from channel count number.
|
||||||
|
pub fn from_count(n: u8) -> Self {
|
||||||
|
match n {
|
||||||
|
1 => AudioChannels::Mono,
|
||||||
|
2 => AudioChannels::Stereo,
|
||||||
|
3 => AudioChannels::Stereo21,
|
||||||
|
4 => AudioChannels::Quad,
|
||||||
|
5 => AudioChannels::Surround50,
|
||||||
|
6 => AudioChannels::Surround51,
|
||||||
|
7 => AudioChannels::Surround61,
|
||||||
|
8 => AudioChannels::Surround71,
|
||||||
|
_ => AudioChannels::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display for AudioChannels is generated by enum_str! macro
|
||||||
|
|
||||||
|
impl SampleRate {
|
||||||
|
/// Parse from MPLS audio_rate byte.
|
||||||
|
pub fn from_audio_rate(ar: u8) -> Self {
|
||||||
|
match ar {
|
||||||
|
1 => SampleRate::S48,
|
||||||
|
4 => SampleRate::S96,
|
||||||
|
5 => SampleRate::S192,
|
||||||
|
12 => SampleRate::S48_192,
|
||||||
|
14 => SampleRate::S48_96,
|
||||||
|
_ => SampleRate::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Sample rate in Hz (primary rate for combo rates).
|
||||||
|
pub fn hz(&self) -> f64 {
|
||||||
|
match self {
|
||||||
|
SampleRate::S44_1 => 44100.0,
|
||||||
|
SampleRate::S48 | SampleRate::S48_96 | SampleRate::S48_192 => 48000.0,
|
||||||
|
SampleRate::S96 => 96000.0,
|
||||||
|
SampleRate::S192 => 192000.0,
|
||||||
|
SampleRate::Unknown => 48000.0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Parse from Hz value.
|
||||||
|
pub fn from_hz(hz: u32) -> Self {
|
||||||
|
match hz {
|
||||||
|
44100 => SampleRate::S44_1,
|
||||||
|
48000 => SampleRate::S48,
|
||||||
|
96000 => SampleRate::S96,
|
||||||
|
192000 => SampleRate::S192,
|
||||||
|
_ => SampleRate::Unknown,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display for SampleRate is generated by enum_str! macro
|
||||||
|
|
||||||
impl HdrFormat {
|
impl HdrFormat {
|
||||||
pub fn name(&self) -> &'static str {
|
pub fn name(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
HdrFormat::Sdr => "SDR",
|
HdrFormat::Sdr => "SDR",
|
||||||
HdrFormat::Hdr10 => "HDR10",
|
HdrFormat::Hdr10 => "HDR10",
|
||||||
|
HdrFormat::Hdr10Plus => "HDR10+",
|
||||||
HdrFormat::DolbyVision => "Dolby Vision",
|
HdrFormat::DolbyVision => "Dolby Vision",
|
||||||
|
HdrFormat::Hlg => "HLG",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const ALL_HDR: &[(&'static str, HdrFormat)] = &[
|
||||||
|
("sdr", HdrFormat::Sdr),
|
||||||
|
("hdr10", HdrFormat::Hdr10),
|
||||||
|
("hdr10+", HdrFormat::Hdr10Plus),
|
||||||
|
("dv", HdrFormat::DolbyVision),
|
||||||
|
("hlg", HdrFormat::Hlg),
|
||||||
|
];
|
||||||
|
|
||||||
|
/// Compact identifier for serialization.
|
||||||
|
pub fn id(&self) -> &'static str {
|
||||||
|
for (id, v) in Self::ALL_HDR {
|
||||||
|
if v == self {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
"sdr"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for HdrFormat {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ColorSpace {
|
impl ColorSpace {
|
||||||
@@ -313,6 +659,131 @@ impl ColorSpace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for ColorSpace {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
f.write_str(self.name())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ─── FromStr impls — single source of truth via ALL_* arrays ───────────────
|
||||||
|
//
|
||||||
|
// Each enum defines a const array of (str, variant) pairs. Display, FromStr,
|
||||||
|
// and id() all derive from this one table — no string appears twice.
|
||||||
|
|
||||||
|
macro_rules! enum_str {
|
||||||
|
($name:ident, $default:expr, [ $( ($s:expr, $v:expr) ),* $(,)? ]) => {
|
||||||
|
impl $name {
|
||||||
|
const ALL: &[(&'static str, $name)] = &[ $( ($s, $v), )* ];
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for $name {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
for (s, v) in $name::ALL {
|
||||||
|
if v == self { return f.write_str(s); }
|
||||||
|
}
|
||||||
|
f.write_str("")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::str::FromStr for $name {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, ()> {
|
||||||
|
for (k, v) in $name::ALL {
|
||||||
|
if *k == s { return Ok(*v); }
|
||||||
|
}
|
||||||
|
Ok($default)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
enum_str!(
|
||||||
|
Resolution,
|
||||||
|
Resolution::Unknown,
|
||||||
|
[
|
||||||
|
("480i", Resolution::R480i),
|
||||||
|
("480p", Resolution::R480p),
|
||||||
|
("576i", Resolution::R576i),
|
||||||
|
("576p", Resolution::R576p),
|
||||||
|
("720p", Resolution::R720p),
|
||||||
|
("1080i", Resolution::R1080i),
|
||||||
|
("1080p", Resolution::R1080p),
|
||||||
|
("2160p", Resolution::R2160p),
|
||||||
|
("4320p", Resolution::R4320p),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
enum_str!(
|
||||||
|
FrameRate,
|
||||||
|
FrameRate::Unknown,
|
||||||
|
[
|
||||||
|
("23.976", FrameRate::F23_976),
|
||||||
|
("24", FrameRate::F24),
|
||||||
|
("25", FrameRate::F25),
|
||||||
|
("29.97", FrameRate::F29_97),
|
||||||
|
("30", FrameRate::F30),
|
||||||
|
("50", FrameRate::F50),
|
||||||
|
("59.94", FrameRate::F59_94),
|
||||||
|
("60", FrameRate::F60),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
enum_str!(
|
||||||
|
AudioChannels,
|
||||||
|
AudioChannels::Unknown,
|
||||||
|
[
|
||||||
|
("mono", AudioChannels::Mono),
|
||||||
|
("stereo", AudioChannels::Stereo),
|
||||||
|
("2.1", AudioChannels::Stereo21),
|
||||||
|
("4.0", AudioChannels::Quad),
|
||||||
|
("5.0", AudioChannels::Surround50),
|
||||||
|
("5.1", AudioChannels::Surround51),
|
||||||
|
("6.1", AudioChannels::Surround61),
|
||||||
|
("7.1", AudioChannels::Surround71),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
enum_str!(
|
||||||
|
SampleRate,
|
||||||
|
SampleRate::Unknown,
|
||||||
|
[
|
||||||
|
("44.1kHz", SampleRate::S44_1),
|
||||||
|
("48kHz", SampleRate::S48),
|
||||||
|
("96kHz", SampleRate::S96),
|
||||||
|
("192kHz", SampleRate::S192),
|
||||||
|
("48/96kHz", SampleRate::S48_96),
|
||||||
|
("48/192kHz", SampleRate::S48_192),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
|
||||||
|
impl std::str::FromStr for Codec {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, ()> {
|
||||||
|
for (id, _, v) in Codec::ALL_CODECS {
|
||||||
|
if *id == s {
|
||||||
|
return Ok(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Codec::Unknown(0))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::str::FromStr for HdrFormat {
|
||||||
|
type Err = ();
|
||||||
|
fn from_str(s: &str) -> std::result::Result<Self, ()> {
|
||||||
|
for (id, v) in HdrFormat::ALL_HDR {
|
||||||
|
if *id == s {
|
||||||
|
return Ok(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Also accept display names
|
||||||
|
for (_id, v) in HdrFormat::ALL_HDR {
|
||||||
|
if HdrFormat::name(v) == s {
|
||||||
|
return Ok(*v);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(HdrFormat::Sdr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl DiscTitle {
|
impl DiscTitle {
|
||||||
/// Empty DiscTitle with no streams.
|
/// Empty DiscTitle with no streams.
|
||||||
pub fn empty() -> Self {
|
pub fn empty() -> Self {
|
||||||
@@ -633,13 +1104,13 @@ impl Disc {
|
|||||||
for title in titles.iter().take(3) {
|
for title in titles.iter().take(3) {
|
||||||
for stream in &title.streams {
|
for stream in &title.streams {
|
||||||
if let Stream::Video(v) = stream {
|
if let Stream::Video(v) = stream {
|
||||||
if v.resolution.contains("2160") {
|
if v.resolution.is_uhd() {
|
||||||
return DiscFormat::Uhd;
|
return DiscFormat::Uhd;
|
||||||
}
|
}
|
||||||
if v.resolution.contains("1080") || v.resolution.contains("720") {
|
if v.resolution.is_hd() {
|
||||||
return DiscFormat::BluRay;
|
return DiscFormat::BluRay;
|
||||||
}
|
}
|
||||||
if v.resolution.contains("480") || v.resolution.contains("576") {
|
if v.resolution.is_sd() {
|
||||||
return DiscFormat::Dvd;
|
return DiscFormat::Dvd;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -782,7 +1253,7 @@ impl Disc {
|
|||||||
/// Reads /sys/block/<dev>/queue/max_hw_sectors_kb on Linux.
|
/// Reads /sys/block/<dev>/queue/max_hw_sectors_kb on Linux.
|
||||||
/// For sg devices, resolves the corresponding block device via sysfs.
|
/// For sg devices, resolves the corresponding block device via sysfs.
|
||||||
/// Returns a value aligned to 3 sectors (one aligned unit).
|
/// Returns a value aligned to 3 sectors (one aligned unit).
|
||||||
pub(crate) fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
pub fn detect_max_batch_sectors(device_path: &str) -> u16 {
|
||||||
let dev_name = device_path.rsplit('/').next().unwrap_or("");
|
let dev_name = device_path.rsplit('/').next().unwrap_or("");
|
||||||
if dev_name.is_empty() {
|
if dev_name.is_empty() {
|
||||||
return DEFAULT_BATCH_SECTORS;
|
return DEFAULT_BATCH_SECTORS;
|
||||||
@@ -850,7 +1321,7 @@ impl<'a> ContentReader<'a> {
|
|||||||
let mut unit = self.read_buf[start..end].to_vec();
|
let mut unit = self.read_buf[start..end].to_vec();
|
||||||
|
|
||||||
// Decrypt if needed
|
// Decrypt if needed
|
||||||
self.decrypt_unit(&mut unit);
|
self.decrypt_unit(&mut unit)?;
|
||||||
self.buf_pos += 1;
|
self.buf_pos += 1;
|
||||||
Ok(Some(unit))
|
Ok(Some(unit))
|
||||||
}
|
}
|
||||||
@@ -871,14 +1342,14 @@ impl<'a> ContentReader<'a> {
|
|||||||
&mut self.read_buf[..total_bytes],
|
&mut self.read_buf[..total_bytes],
|
||||||
&self.decrypt_keys,
|
&self.decrypt_keys,
|
||||||
self.unit_key_idx,
|
self.unit_key_idx,
|
||||||
);
|
)?;
|
||||||
self.buf_pos = self.buf_len;
|
self.buf_pos = self.buf_len;
|
||||||
Ok(Some(&self.read_buf[..total_bytes]))
|
Ok(Some(&self.read_buf[..total_bytes]))
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decrypt a single aligned unit in-place if needed.
|
/// Decrypt a single aligned unit in-place if needed.
|
||||||
fn decrypt_unit(&self, unit: &mut [u8]) {
|
fn decrypt_unit(&self, unit: &mut [u8]) -> Result<()> {
|
||||||
crate::decrypt::decrypt_sectors(unit, &self.decrypt_keys, self.unit_key_idx);
|
crate::decrypt::decrypt_sectors(unit, &self.decrypt_keys, self.unit_key_idx)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Read sectors via standard READ(10) 0x00.
|
/// Read sectors via standard READ(10) 0x00.
|
||||||
@@ -1004,60 +1475,14 @@ impl<'a> ContentReader<'a> {
|
|||||||
|
|
||||||
// ─── Format helpers ────────────────────────────────────────────────────────
|
// ─── Format helpers ────────────────────────────────────────────────────────
|
||||||
|
|
||||||
fn format_resolution(video_format: u8, _video_rate: u8) -> String {
|
// Old format_* functions replaced by Resolution/FrameRate/AudioChannels/SampleRate enums
|
||||||
match video_format {
|
|
||||||
1 => "480i".into(),
|
|
||||||
2 => "576i".into(),
|
|
||||||
3 => "480p".into(),
|
|
||||||
4 => "1080i".into(),
|
|
||||||
5 => "720p".into(),
|
|
||||||
6 => "1080p".into(),
|
|
||||||
7 => "576p".into(),
|
|
||||||
8 => "2160p".into(),
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_framerate(video_rate: u8) -> String {
|
|
||||||
match video_rate {
|
|
||||||
1 => "23.976".into(),
|
|
||||||
2 => "24".into(),
|
|
||||||
3 => "25".into(),
|
|
||||||
4 => "29.97".into(),
|
|
||||||
6 => "50".into(),
|
|
||||||
7 => "59.94".into(),
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_channels(audio_format: u8) -> String {
|
|
||||||
match audio_format {
|
|
||||||
1 => "mono".into(),
|
|
||||||
3 => "stereo".into(),
|
|
||||||
6 => "5.1".into(),
|
|
||||||
12 => "7.1".into(),
|
|
||||||
_ if audio_format > 0 => format!("{audio_format}ch"),
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn format_samplerate(audio_rate: u8) -> String {
|
|
||||||
match audio_rate {
|
|
||||||
1 => "48kHz".into(),
|
|
||||||
4 => "96kHz".into(),
|
|
||||||
5 => "192kHz".into(),
|
|
||||||
12 => "48/192kHz".into(),
|
|
||||||
14 => "48/96kHz".into(),
|
|
||||||
_ => String::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|
||||||
/// Helper: build a DiscTitle with a single video stream at the given resolution.
|
/// Helper: build a DiscTitle with a single video stream at the given resolution.
|
||||||
fn title_with_video(codec: Codec, resolution: &str) -> DiscTitle {
|
fn title_with_video(codec: Codec, resolution: Resolution) -> DiscTitle {
|
||||||
DiscTitle {
|
DiscTitle {
|
||||||
playlist: "00800.mpls".into(),
|
playlist: "00800.mpls".into(),
|
||||||
playlist_id: 800,
|
playlist_id: 800,
|
||||||
@@ -1067,8 +1492,8 @@ mod tests {
|
|||||||
streams: vec![Stream::Video(VideoStream {
|
streams: vec![Stream::Video(VideoStream {
|
||||||
pid: 0x1011,
|
pid: 0x1011,
|
||||||
codec,
|
codec,
|
||||||
resolution: resolution.into(),
|
resolution,
|
||||||
frame_rate: "23.976".into(),
|
frame_rate: FrameRate::F23_976,
|
||||||
hdr: HdrFormat::Sdr,
|
hdr: HdrFormat::Sdr,
|
||||||
color_space: ColorSpace::Bt709,
|
color_space: ColorSpace::Bt709,
|
||||||
secondary: false,
|
secondary: false,
|
||||||
@@ -1082,19 +1507,19 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_format_uhd() {
|
fn detect_format_uhd() {
|
||||||
let titles = vec![title_with_video(Codec::Hevc, "2160p")];
|
let titles = vec![title_with_video(Codec::Hevc, Resolution::R2160p)];
|
||||||
assert_eq!(Disc::detect_format(&titles), DiscFormat::Uhd);
|
assert_eq!(Disc::detect_format(&titles), DiscFormat::Uhd);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_format_bluray() {
|
fn detect_format_bluray() {
|
||||||
let titles = vec![title_with_video(Codec::H264, "1080p")];
|
let titles = vec![title_with_video(Codec::H264, Resolution::R1080p)];
|
||||||
assert_eq!(Disc::detect_format(&titles), DiscFormat::BluRay);
|
assert_eq!(Disc::detect_format(&titles), DiscFormat::BluRay);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn detect_format_dvd() {
|
fn detect_format_dvd() {
|
||||||
let titles = vec![title_with_video(Codec::Mpeg2, "480i")];
|
let titles = vec![title_with_video(Codec::Mpeg2, Resolution::R480i)];
|
||||||
assert_eq!(Disc::detect_format(&titles), DiscFormat::Dvd);
|
assert_eq!(Disc::detect_format(&titles), DiscFormat::Dvd);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1106,7 +1531,7 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn content_format_default_bdts() {
|
fn content_format_default_bdts() {
|
||||||
let t = title_with_video(Codec::H264, "1080p");
|
let t = title_with_video(Codec::H264, Resolution::R1080p);
|
||||||
assert_eq!(t.content_format, ContentFormat::BdTs);
|
assert_eq!(t.content_format, ContentFormat::BdTs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1114,7 +1539,7 @@ mod tests {
|
|||||||
fn content_format_dvd_mpegps() {
|
fn content_format_dvd_mpegps() {
|
||||||
let t = DiscTitle {
|
let t = DiscTitle {
|
||||||
content_format: ContentFormat::MpegPs,
|
content_format: ContentFormat::MpegPs,
|
||||||
..title_with_video(Codec::Mpeg2, "480i")
|
..title_with_video(Codec::Mpeg2, Resolution::R480i)
|
||||||
};
|
};
|
||||||
assert_eq!(t.content_format, ContentFormat::MpegPs);
|
assert_eq!(t.content_format, ContentFormat::MpegPs);
|
||||||
}
|
}
|
||||||
|
|||||||
+17
-38
@@ -55,6 +55,7 @@ pub const E_AACS_VID_READ: u16 = 7009;
|
|||||||
pub const E_AACS_VID_MAC: u16 = 7010;
|
pub const E_AACS_VID_MAC: u16 = 7010;
|
||||||
pub const E_AACS_DATA_KEY: u16 = 7011;
|
pub const E_AACS_DATA_KEY: u16 = 7011;
|
||||||
pub const E_AACS_VUK_DERIVE: u16 = 7012;
|
pub const E_AACS_VUK_DERIVE: u16 = 7012;
|
||||||
|
pub const E_DECRYPT_FAILED: u16 = 7013;
|
||||||
// Keydb (8xxx)
|
// Keydb (8xxx)
|
||||||
pub const E_KEYDB_CONNECT: u16 = 8000;
|
pub const E_KEYDB_CONNECT: u16 = 8000;
|
||||||
pub const E_KEYDB_HTTP: u16 = 8001;
|
pub const E_KEYDB_HTTP: u16 = 8001;
|
||||||
@@ -72,13 +73,9 @@ pub const E_MUX_WRITE: u16 = 9001;
|
|||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Error {
|
pub enum Error {
|
||||||
/// Device not found at the given path.
|
/// Device not found at the given path.
|
||||||
DeviceNotFound {
|
DeviceNotFound { path: String },
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
/// Insufficient permissions to open the device.
|
/// Insufficient permissions to open the device.
|
||||||
DevicePermission {
|
DevicePermission { path: String },
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Drive model is not in the profile database.
|
/// Drive model is not in the profile database.
|
||||||
UnsupportedDrive {
|
UnsupportedDrive {
|
||||||
@@ -98,10 +95,7 @@ pub enum Error {
|
|||||||
/// Drive unlock (firmware upload) failed.
|
/// Drive unlock (firmware upload) failed.
|
||||||
UnlockFailed,
|
UnlockFailed,
|
||||||
/// Firmware signature verification failed.
|
/// Firmware signature verification failed.
|
||||||
SignatureMismatch {
|
SignatureMismatch { expected: [u8; 4], got: [u8; 4] },
|
||||||
expected: [u8; 4],
|
|
||||||
got: [u8; 4],
|
|
||||||
},
|
|
||||||
/// Operation requires an unlocked drive.
|
/// Operation requires an unlocked drive.
|
||||||
NotUnlocked,
|
NotUnlocked,
|
||||||
/// Operation requires a calibrated drive.
|
/// Operation requires a calibrated drive.
|
||||||
@@ -114,36 +108,25 @@ pub enum Error {
|
|||||||
sense_key: u8,
|
sense_key: u8,
|
||||||
},
|
},
|
||||||
/// SCSI command timed out.
|
/// SCSI command timed out.
|
||||||
ScsiTimeout {
|
ScsiTimeout { opcode: u8 },
|
||||||
opcode: u8,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Underlying I/O error.
|
/// Underlying I/O error.
|
||||||
IoError {
|
IoError { source: std::io::Error },
|
||||||
source: std::io::Error,
|
|
||||||
},
|
|
||||||
/// Write operation failed.
|
/// Write operation failed.
|
||||||
WriteError,
|
WriteError,
|
||||||
|
|
||||||
/// Failed to read disc sector.
|
/// Failed to read disc sector.
|
||||||
DiscRead {
|
DiscRead { sector: u64 },
|
||||||
sector: u64,
|
|
||||||
},
|
|
||||||
/// MPLS playlist parsing failed.
|
/// MPLS playlist parsing failed.
|
||||||
MplsParse,
|
MplsParse,
|
||||||
/// CLPI clip info parsing failed.
|
/// CLPI clip info parsing failed.
|
||||||
ClpiParse,
|
ClpiParse,
|
||||||
/// File not found on the UDF filesystem.
|
/// File not found on the UDF filesystem.
|
||||||
UdfNotFound {
|
UdfNotFound { path: String },
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
/// Disc contains no playable titles.
|
/// Disc contains no playable titles.
|
||||||
DiscNoTitles,
|
DiscNoTitles,
|
||||||
/// Title index out of range.
|
/// Title index out of range.
|
||||||
DiscTitleRange {
|
DiscTitleRange { index: usize, count: usize },
|
||||||
index: usize,
|
|
||||||
count: usize,
|
|
||||||
},
|
|
||||||
/// Title has no sector extents to read.
|
/// Title has no sector extents to read.
|
||||||
DiscNoExtents,
|
DiscNoExtents,
|
||||||
/// DVD IFO file parsing failed.
|
/// DVD IFO file parsing failed.
|
||||||
@@ -175,27 +158,21 @@ pub enum Error {
|
|||||||
AacsDataKey,
|
AacsDataKey,
|
||||||
/// Failed to derive the Volume Unique Key.
|
/// Failed to derive the Volume Unique Key.
|
||||||
AacsVukDerive,
|
AacsVukDerive,
|
||||||
|
/// Decryption failed — missing or invalid keys.
|
||||||
|
DecryptFailed { reason: String },
|
||||||
|
|
||||||
/// Failed to connect to the KEYDB server.
|
/// Failed to connect to the KEYDB server.
|
||||||
KeydbConnect {
|
KeydbConnect { host: String },
|
||||||
host: String,
|
|
||||||
},
|
|
||||||
/// KEYDB server returned an HTTP error.
|
/// KEYDB server returned an HTTP error.
|
||||||
KeydbHttp {
|
KeydbHttp { status: u16 },
|
||||||
status: u16,
|
|
||||||
},
|
|
||||||
/// Downloaded KEYDB file is invalid (no entries found).
|
/// Downloaded KEYDB file is invalid (no entries found).
|
||||||
KeydbInvalid,
|
KeydbInvalid,
|
||||||
/// Failed to write KEYDB to disk.
|
/// Failed to write KEYDB to disk.
|
||||||
KeydbWrite {
|
KeydbWrite { path: String },
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
/// Failed to parse KEYDB file.
|
/// Failed to parse KEYDB file.
|
||||||
KeydbParse,
|
KeydbParse,
|
||||||
/// Failed to load KEYDB from disk.
|
/// Failed to load KEYDB from disk.
|
||||||
KeydbLoad {
|
KeydbLoad { path: String },
|
||||||
path: String,
|
|
||||||
},
|
|
||||||
|
|
||||||
/// Lookahead buffer exhausted before codec headers found.
|
/// Lookahead buffer exhausted before codec headers found.
|
||||||
MuxLookahead,
|
MuxLookahead,
|
||||||
@@ -240,6 +217,7 @@ impl Error {
|
|||||||
Error::AacsVidMac => E_AACS_VID_MAC,
|
Error::AacsVidMac => E_AACS_VID_MAC,
|
||||||
Error::AacsDataKey => E_AACS_DATA_KEY,
|
Error::AacsDataKey => E_AACS_DATA_KEY,
|
||||||
Error::AacsVukDerive => E_AACS_VUK_DERIVE,
|
Error::AacsVukDerive => E_AACS_VUK_DERIVE,
|
||||||
|
Error::DecryptFailed { .. } => E_DECRYPT_FAILED,
|
||||||
Error::KeydbConnect { .. } => E_KEYDB_CONNECT,
|
Error::KeydbConnect { .. } => E_KEYDB_CONNECT,
|
||||||
Error::KeydbHttp { .. } => E_KEYDB_HTTP,
|
Error::KeydbHttp { .. } => E_KEYDB_HTTP,
|
||||||
Error::KeydbInvalid => E_KEYDB_INVALID,
|
Error::KeydbInvalid => E_KEYDB_INVALID,
|
||||||
@@ -318,6 +296,7 @@ impl std::fmt::Display for Error {
|
|||||||
Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status),
|
Error::KeydbHttp { status } => write!(f, "E{}: {}", self.code(), status),
|
||||||
Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path),
|
Error::KeydbWrite { path } => write!(f, "E{}: {}", self.code(), path),
|
||||||
Error::KeydbLoad { path } => write!(f, "E{}: {}", self.code(), path),
|
Error::KeydbLoad { path } => write!(f, "E{}: {}", self.code(), path),
|
||||||
|
Error::DecryptFailed { reason } => write!(f, "E{}: {}", self.code(), reason),
|
||||||
// Simple codes — no extra data
|
// Simple codes — no extra data
|
||||||
_ => write!(f, "E{}", self.code()),
|
_ => write!(f, "E{}", self.code()),
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-1
@@ -205,7 +205,8 @@ impl IsoStream {
|
|||||||
|
|
||||||
// Decrypt after read — stream handles its own decryption
|
// Decrypt after read — stream handles its own decryption
|
||||||
let bytes = count as usize * SECTOR_SIZE as usize;
|
let bytes = count as usize * SECTOR_SIZE as usize;
|
||||||
decrypt_sectors(&mut self.batch_buf[..bytes], &self.decrypt_keys, 0);
|
decrypt_sectors(&mut self.batch_buf[..bytes], &self.decrypt_keys, 0)
|
||||||
|
.map_err(|e| io::Error::other(e.to_string()))?;
|
||||||
|
|
||||||
self.buf_pos = 0;
|
self.buf_pos = 0;
|
||||||
self.buf_len = bytes;
|
self.buf_len = bytes;
|
||||||
|
|||||||
Reference in New Issue
Block a user