use std::collections::BTreeMap;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
use std::path::Path;
use crate::disc::{DvdDisc, DvdFileKind};
use crate::error::{Error, Result};
use crate::ifo::{PgcTime, DVD_SECTOR};
pub const SC_PACK_HEADER: u8 = 0xBA;
pub const SC_SYSTEM_HEADER: u8 = 0xBB;
pub const SC_PROGRAM_STREAM_MAP: u8 = 0xBC;
pub const SC_PRIVATE_STREAM_1: u8 = 0xBD;
pub const SC_PADDING_STREAM: u8 = 0xBE;
pub const SC_PRIVATE_STREAM_2: u8 = 0xBF;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PackHeader {
pub scr_base: u64,
pub scr_ext: u16,
pub mux_rate: u32,
pub stuffing_bytes: u8,
}
impl PackHeader {
pub const SIZE: usize = 14;
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("VOB pack: shorter than 14-byte header"));
}
if buf[0..4] != [0x00, 0x00, 0x01, SC_PACK_HEADER] {
return Err(Error::InvalidUdf("VOB pack: missing 0x000001BA start code"));
}
let b4 = buf[4] as u64;
let b5 = buf[5] as u64;
let b6 = buf[6] as u64;
let b7 = buf[7] as u64;
let b8 = buf[8] as u64;
let b9 = buf[9] as u64;
if (b4 >> 6) != 0b01 {
return Err(Error::InvalidUdf(
"VOB pack: byte 4 top bits != 01 (not MPEG-2)",
));
}
let scr_high = (b4 >> 3) & 0b111;
if (b4 >> 2) & 0b1 != 1 {
return Err(Error::InvalidUdf("VOB pack: SCR marker bit 1 missing"));
}
let scr_mid = ((b4 & 0b11) << 13) | (b5 << 5) | ((b6 >> 3) & 0b1_1111);
if (b6 >> 2) & 0b1 != 1 {
return Err(Error::InvalidUdf("VOB pack: SCR marker bit 2 missing"));
}
let scr_low = ((b6 & 0b11) << 13) | (b7 << 5) | ((b8 >> 3) & 0b1_1111);
if (b8 >> 2) & 0b1 != 1 {
return Err(Error::InvalidUdf("VOB pack: SCR marker bit 3 missing"));
}
let scr_ext_v = ((b8 & 0b11) << 7) | (b9 >> 1);
if b9 & 0b1 != 1 {
return Err(Error::InvalidUdf("VOB pack: SCR marker bit 4 missing"));
}
let scr_base = (scr_high << 30) | (scr_mid << 15) | scr_low;
let scr_ext = scr_ext_v as u16;
let mux_rate = ((buf[10] as u32) << 14) | ((buf[11] as u32) << 6) | ((buf[12] as u32) >> 2);
if buf[12] & 0b11 != 0b11 {
return Err(Error::InvalidUdf(
"VOB pack: mux_rate trailing marker bits != 11",
));
}
if mux_rate == 0 {
return Err(Error::InvalidUdf("VOB pack: program_mux_rate is 0"));
}
let stuffing_bytes = buf[13] & 0b111;
Ok(Self {
scr_base,
scr_ext,
mux_rate,
stuffing_bytes,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum DvdSubstream {
Subpicture(u8),
Ac3(u8),
Dts(u8),
Lpcm(u8),
}
impl DvdSubstream {
pub fn from_first_byte(b: u8) -> Option<Self> {
match b {
0x20..=0x3F => Some(Self::Subpicture(b)),
0x80..=0x87 => Some(Self::Ac3(b)),
0x88..=0x8F => Some(Self::Dts(b)),
0xA0..=0xA7 => Some(Self::Lpcm(b)),
_ => None,
}
}
pub fn track(self) -> u8 {
match self {
Self::Subpicture(b) => b - 0x20,
Self::Ac3(b) => b - 0x80,
Self::Dts(b) => b - 0x88,
Self::Lpcm(b) => b - 0xA0,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct PesPacket<'a> {
pub stream_id: u8,
pub pts: Option<u64>,
pub dts: Option<u64>,
pub payload: &'a [u8],
pub wire_size: usize,
}
impl<'a> PesPacket<'a> {
pub fn parse(buf: &'a [u8]) -> Result<Self> {
if buf.len() < 6 {
return Err(Error::InvalidUdf("PES: shorter than 6-byte header"));
}
if buf[0..3] != [0x00, 0x00, 0x01] {
return Err(Error::InvalidUdf("PES: missing 0x000001 start code"));
}
let stream_id = buf[3];
let pkt_len = ((buf[4] as usize) << 8) | (buf[5] as usize);
let wire_size = 6 + pkt_len;
if buf.len() < wire_size {
return Err(Error::InvalidUdf(
"PES: PES_packet_length exceeds available buffer",
));
}
if stream_id == SC_PADDING_STREAM || stream_id == SC_PRIVATE_STREAM_2 {
return Ok(Self {
stream_id,
pts: None,
dts: None,
payload: &buf[6..wire_size],
wire_size,
});
}
if buf.len() < 9 {
return Err(Error::InvalidUdf(
"PES: extension stream shorter than 9-byte header",
));
}
if (buf[6] >> 6) != 0b10 {
return Err(Error::InvalidUdf(
"PES: byte-6 top bits != 10 (MPEG-1 framing not supported)",
));
}
let pts_dts_flags = (buf[7] >> 6) & 0b11;
let header_data_len = buf[8] as usize;
let payload_start = 9 + header_data_len;
if payload_start > wire_size {
return Err(Error::InvalidUdf(
"PES: PES_header_data_length exceeds packet length",
));
}
let (pts, dts) = match pts_dts_flags {
0b00 => (None, None),
0b01 => return Err(Error::InvalidUdf("PES: PTS_DTS_flags == 01 is forbidden")),
0b10 => {
if header_data_len < 5 {
return Err(Error::InvalidUdf("PES: PTS flagged but data_len < 5"));
}
let pts_v = parse_timestamp(&buf[9..14], 0b0010)?;
(Some(pts_v), None)
}
0b11 => {
if header_data_len < 10 {
return Err(Error::InvalidUdf("PES: PTS+DTS flagged but data_len < 10"));
}
let pts_v = parse_timestamp(&buf[9..14], 0b0011)?;
let dts_v = parse_timestamp(&buf[14..19], 0b0001)?;
(Some(pts_v), Some(dts_v))
}
_ => unreachable!(),
};
Ok(Self {
stream_id,
pts,
dts,
payload: &buf[payload_start..wire_size],
wire_size,
})
}
pub fn dvd_substream(&self) -> Option<DvdSubstream> {
if self.stream_id != SC_PRIVATE_STREAM_1 {
return None;
}
self.payload
.first()
.and_then(|b| DvdSubstream::from_first_byte(*b))
}
}
fn parse_timestamp(buf: &[u8], expected_tag: u8) -> Result<u64> {
if buf.len() < 5 {
return Err(Error::InvalidUdf("PTS/DTS field shorter than 5 bytes"));
}
let tag = buf[0] >> 4;
if tag != expected_tag {
return Err(Error::InvalidUdf(
"PTS/DTS leading tag does not match flags",
));
}
if buf[0] & 1 != 1 || buf[2] & 1 != 1 || buf[4] & 1 != 1 {
return Err(Error::InvalidUdf("PTS/DTS marker bit missing"));
}
let ts_high = ((buf[0] >> 1) & 0b111) as u64;
let ts_mid = (((buf[1] as u64) << 7) | ((buf[2] as u64) >> 1)) & 0x7FFF;
let ts_low = (((buf[3] as u64) << 7) | ((buf[4] as u64) >> 1)) & 0x7FFF;
Ok((ts_high << 30) | (ts_mid << 15) | ts_low)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SlColiCell {
pub color: u8,
pub contrast: u8,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SlColi {
pub selection: [SlColiCell; 4],
pub action: [SlColiCell; 4],
}
impl SlColi {
fn parse(b: &[u8; 8]) -> Self {
let mut selection = [SlColiCell::default(); 4];
let mut action = [SlColiCell::default(); 4];
selection[3].color = b[0] >> 4;
selection[2].color = b[0] & 0x0F;
selection[1].color = b[1] >> 4;
selection[0].color = b[1] & 0x0F;
selection[3].contrast = b[2] >> 4;
selection[2].contrast = b[2] & 0x0F;
selection[1].contrast = b[3] >> 4;
selection[0].contrast = b[3] & 0x0F;
action[3].color = b[4] >> 4;
action[2].color = b[4] & 0x0F;
action[1].color = b[5] >> 4;
action[0].color = b[5] & 0x0F;
action[3].contrast = b[6] >> 4;
action[2].contrast = b[6] & 0x0F;
action[1].contrast = b[7] >> 4;
action[0].contrast = b[7] & 0x0F;
Self { selection, action }
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct ButtonInfo {
pub btn_coln: u8,
pub start_x: u16,
pub end_x: u16,
pub start_y: u16,
pub end_y: u16,
pub auto_action: bool,
pub up: u8,
pub down: u8,
pub left: u8,
pub right: u8,
pub command: [u8; 8],
}
impl ButtonInfo {
fn parse(b: &[u8; 18]) -> Self {
let start_x = (((b[0] & 0x3F) as u16) << 4) | ((b[1] >> 4) as u16);
let end_x = (((b[1] & 0x03) as u16) << 8) | (b[2] as u16);
let start_y = (((b[3] & 0x3F) as u16) << 4) | ((b[4] >> 4) as u16);
let end_y = (((b[4] & 0x03) as u16) << 8) | (b[5] as u16);
let mut command = [0u8; 8];
command.copy_from_slice(&b[0x0A..0x12]);
Self {
btn_coln: b[0] >> 6,
start_x,
end_x,
start_y,
end_y,
auto_action: (b[3] >> 6) != 0,
up: b[6] & 0x3F,
down: b[7] & 0x3F,
left: b[8] & 0x3F,
right: b[9] & 0x3F,
command,
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct HighlightInfo {
pub hli_s_ptm: u32,
pub hli_e_ptm: u32,
pub btn_sl_e_ptm: u32,
pub btn_md: u16,
pub btn_sn: u8,
pub btn_ns: u8,
pub nsl_btn_ns: u8,
pub fosl_btnn: u8,
pub foac_btnn: u8,
pub sl_coli: [SlColi; 3],
pub buttons: Vec<ButtonInfo>,
}
impl HighlightInfo {
#[inline]
pub const fn button_mode(&self) -> ButtonMode {
ButtonMode::from_btn_md(self.btn_md)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum HighlightStatus {
None,
AllNew,
UsePrevious,
UsePreviousExceptCommands,
}
impl HighlightStatus {
#[inline]
pub const fn from_hli_ss(hli_ss: u16) -> Self {
match hli_ss & 0b11 {
0b00 => Self::None,
0b01 => Self::AllNew,
0b10 => Self::UsePrevious,
0b11 => Self::UsePreviousExceptCommands,
_ => unreachable!(),
}
}
#[inline]
pub const fn is_none(self) -> bool {
matches!(self, Self::None)
}
#[inline]
pub const fn declares_new_geometry(self) -> bool {
matches!(self, Self::AllNew)
}
#[inline]
pub const fn reuses_previous_geometry(self) -> bool {
matches!(self, Self::UsePrevious | Self::UsePreviousExceptCommands)
}
#[inline]
pub const fn supplies_own_commands(self) -> bool {
matches!(self, Self::AllNew | Self::UsePreviousExceptCommands)
}
#[inline]
pub const fn to_bits(self) -> u16 {
match self {
Self::None => 0b00,
Self::AllNew => 0b01,
Self::UsePrevious => 0b10,
Self::UsePreviousExceptCommands => 0b11,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct ButtonMode {
pub group_count: u8,
pub group_types: [u8; 3],
}
impl ButtonMode {
#[inline]
pub const fn from_btn_md(btn_md: u16) -> Self {
Self {
group_count: ((btn_md >> 12) & 0x3) as u8,
group_types: [
((btn_md >> 8) & 0x7) as u8,
((btn_md >> 4) & 0x7) as u8,
(btn_md & 0x7) as u8,
],
}
}
#[inline]
pub const fn to_btn_md(self) -> u16 {
(((self.group_count & 0x3) as u16) << 12)
| (((self.group_types[0] & 0x7) as u16) << 8)
| (((self.group_types[1] & 0x7) as u16) << 4)
| ((self.group_types[2] & 0x7) as u16)
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct NsmlAngleCell {
pub dsta: u32,
}
impl NsmlAngleCell {
pub const ABSENT: u32 = 0x0000_0000;
pub const NO_MORE_VIDEO: u32 = 0x7FFF_FFFF;
#[inline]
pub fn is_absent(&self) -> bool {
self.dsta == Self::ABSENT
}
#[inline]
pub fn is_no_more_video(&self) -> bool {
self.dsta == Self::NO_MORE_VIDEO
}
#[inline]
pub fn is_backward(&self) -> bool {
self.dsta & 0x8000_0000 != 0
}
#[inline]
pub fn offset_sectors(&self) -> Option<u32> {
if self.is_absent() || self.is_no_more_video() {
None
} else {
Some(self.dsta & 0x7FFF_FFFF)
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct NsmlAgli {
pub cells: [NsmlAngleCell; 9],
}
impl NsmlAgli {
pub const PACKET_OFFSET: usize = 0x3C;
pub const SIZE: usize = 9 * 4;
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::PACKET_OFFSET + Self::SIZE {
return Err(Error::InvalidUdf("NSML_AGLI: short buffer"));
}
let mut cells = [NsmlAngleCell::default(); 9];
for (i, slot) in cells.iter_mut().enumerate() {
*slot = NsmlAngleCell {
dsta: read_u32_be(buf, Self::PACKET_OFFSET + i * 4)?,
};
}
Ok(Self { cells })
}
#[inline]
pub fn is_empty(&self) -> bool {
self.cells.iter().all(NsmlAngleCell::is_absent)
}
#[inline]
pub fn active_angle_count(&self) -> usize {
self.cells
.iter()
.filter(|c| !c.is_absent() && !c.is_no_more_video())
.count()
}
#[inline]
pub fn angle(&self, angle: u8) -> Option<NsmlAngleCell> {
if (1..=9).contains(&angle) {
Some(self.cells[angle as usize - 1])
} else {
None
}
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct PciPacket {
pub nv_pck_lbn: u32,
pub vobu_cat: u16,
pub vobu_uop_ctl: u32,
pub vobu_s_ptm: u32,
pub vobu_e_ptm: u32,
pub vobu_se_e_ptm: u32,
pub c_eltm: u32,
pub nsml_agli: NsmlAgli,
pub hli_ss: u16,
pub highlight: Option<HighlightInfo>,
}
impl PciPacket {
const HLI_GI: usize = 0x60;
const SL_COLI: usize = 0x76;
const BTN_IT: usize = 0x8E;
const BTN_IT_SIZE: usize = 18;
const MAX_BUTTONS: usize = 36;
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::HLI_GI + 2 {
return Err(Error::InvalidUdf(
"PCI: shorter than PCI_GI + HLI_GI prefix",
));
}
let hli_ss = read_u16_be(buf, Self::HLI_GI)?;
let nsml_agli = NsmlAgli::parse(buf)?;
let highlight = Self::parse_highlight(buf)?;
Ok(Self {
nv_pck_lbn: read_u32_be(buf, 0x00)?,
vobu_cat: read_u16_be(buf, 0x04)?,
vobu_uop_ctl: read_u32_be(buf, 0x08)?,
vobu_s_ptm: read_u32_be(buf, 0x0C)?,
vobu_e_ptm: read_u32_be(buf, 0x10)?,
vobu_se_e_ptm: read_u32_be(buf, 0x14)?,
c_eltm: read_u32_be(buf, 0x18)?,
nsml_agli,
hli_ss,
highlight,
})
}
#[inline]
pub fn uop_mask(&self) -> crate::uops::UopMask {
crate::uops::UopMask::from_bits(self.vobu_uop_ctl)
}
#[inline]
pub fn is_user_op_allowed(&self, op: crate::uops::UserOp) -> bool {
self.uop_mask().is_allowed(op)
}
#[inline]
pub fn highlight_status(&self) -> HighlightStatus {
HighlightStatus::from_hli_ss(self.hli_ss)
}
fn parse_highlight(buf: &[u8]) -> Result<Option<HighlightInfo>> {
let btn_ns_off = Self::HLI_GI + 0x11;
if buf.len() <= btn_ns_off {
return Ok(None);
}
let btn_ns = buf[btn_ns_off];
if btn_ns == 0 {
return Ok(None);
}
if btn_ns as usize > Self::MAX_BUTTONS {
return Err(Error::InvalidUdf("PCI HLI: btn_ns exceeds 36"));
}
let need = Self::BTN_IT + (btn_ns as usize) * Self::BTN_IT_SIZE;
if buf.len() < need {
return Err(Error::InvalidUdf(
"PCI HLI: body shorter than declared BTN_IT table",
));
}
let mut sl_coli = [SlColi::default(); 3];
for (i, scheme) in sl_coli.iter_mut().enumerate() {
let off = Self::SL_COLI + i * 8;
let cell: [u8; 8] = buf[off..off + 8].try_into().unwrap();
*scheme = SlColi::parse(&cell);
}
let mut buttons = Vec::with_capacity(btn_ns as usize);
for i in 0..btn_ns as usize {
let off = Self::BTN_IT + i * Self::BTN_IT_SIZE;
let entry: [u8; 18] = buf[off..off + Self::BTN_IT_SIZE].try_into().unwrap();
buttons.push(ButtonInfo::parse(&entry));
}
Ok(Some(HighlightInfo {
hli_s_ptm: read_u32_be(buf, Self::HLI_GI + 0x02)?,
hli_e_ptm: read_u32_be(buf, Self::HLI_GI + 0x06)?,
btn_sl_e_ptm: read_u32_be(buf, Self::HLI_GI + 0x0A)?,
btn_md: read_u16_be(buf, Self::HLI_GI + 0x0E)?,
btn_sn: buf[Self::HLI_GI + 0x10],
btn_ns,
nsl_btn_ns: buf[Self::HLI_GI + 0x12],
fosl_btnn: buf[Self::HLI_GI + 0x14],
foac_btnn: buf[Self::HLI_GI + 0x15],
sl_coli,
buttons,
}))
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DsiGi {
pub nv_pck_scr: u32,
pub nv_pck_lbn: u32,
pub vobu_ea: u32,
pub vobu_1stref_ea: u32,
pub vobu_2ndref_ea: u32,
pub vobu_3rdref_ea: u32,
pub vobu_vob_idn: u16,
pub vobu_c_idn: u8,
pub c_eltm: u32,
}
impl DsiGi {
pub const SIZE: usize = 0x20;
pub fn cell_elapsed_time(&self) -> PgcTime {
PgcTime::from_bytes(self.c_eltm.to_be_bytes())
}
pub fn cell_elapsed_ns(&self) -> u64 {
self.cell_elapsed_time().to_nanoseconds()
}
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("DSI_GI: short buffer"));
}
Ok(Self {
nv_pck_scr: read_u32_be(buf, 0x00)?,
nv_pck_lbn: read_u32_be(buf, 0x04)?,
vobu_ea: read_u32_be(buf, 0x08)?,
vobu_1stref_ea: read_u32_be(buf, 0x0C)?,
vobu_2ndref_ea: read_u32_be(buf, 0x10)?,
vobu_3rdref_ea: read_u32_be(buf, 0x14)?,
vobu_vob_idn: read_u16_be(buf, 0x18)?,
vobu_c_idn: buf[0x1B],
c_eltm: read_u32_be(buf, 0x1C)?,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SmlAudioGap {
pub stp_ptm1: u32,
pub stp_ptm2: u32,
pub gap_len1: u32,
pub gap_len2: u32,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SmlPbi {
pub ilvu: u16,
pub ilvu_ea: u32,
pub nxt_ilvu_sa: u32,
pub nxt_ilvu_sz: u16,
pub vob_v_s_ptm: u32,
pub vob_v_e_ptm: u32,
pub audio_gaps: [SmlAudioGap; 8],
}
impl SmlPbi {
pub const SIZE: usize = 0xB4 - 0x20;
pub const PACKET_OFFSET: usize = 0x20;
pub fn preu(&self) -> bool {
self.ilvu & 0x8000 != 0
}
pub fn is_ilvu(&self) -> bool {
self.ilvu & 0x4000 != 0
}
pub fn unit_start(&self) -> bool {
self.ilvu & 0x2000 != 0
}
pub fn unit_end(&self) -> bool {
self.ilvu & 0x1000 != 0
}
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("SML_PBI: short buffer"));
}
let mut audio_gaps = [SmlAudioGap::default(); 8];
for (i, slot) in audio_gaps.iter_mut().enumerate() {
let base = 0x14 + i * 16;
*slot = SmlAudioGap {
stp_ptm1: read_u32_be(buf, base)?,
stp_ptm2: read_u32_be(buf, base + 4)?,
gap_len1: read_u32_be(buf, base + 8)?,
gap_len2: read_u32_be(buf, base + 12)?,
};
}
Ok(Self {
ilvu: read_u16_be(buf, 0x00)?,
ilvu_ea: read_u32_be(buf, 0x02)?,
nxt_ilvu_sa: read_u32_be(buf, 0x06)?,
nxt_ilvu_sz: read_u16_be(buf, 0x0A)?,
vob_v_s_ptm: read_u32_be(buf, 0x0C)?,
vob_v_e_ptm: read_u32_be(buf, 0x10)?,
audio_gaps,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct SmlAngleCell {
pub dsta: u32,
pub sz: u16,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SmlAgli {
pub cells: [SmlAngleCell; 9],
}
impl SmlAgli {
pub const SIZE: usize = 0xEA - 0xB4;
pub const PACKET_OFFSET: usize = 0xB4;
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("SML_AGLI: short buffer"));
}
let mut cells = [SmlAngleCell::default(); 9];
for (i, slot) in cells.iter_mut().enumerate() {
let base = i * 6;
*slot = SmlAngleCell {
dsta: read_u32_be(buf, base)?,
sz: read_u16_be(buf, base + 4)?,
};
}
Ok(Self { cells })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct VobuSri {
pub sri_nvwv: u32,
pub forward: [u32; 19],
pub sri_nv: u32,
pub sri_pv: u32,
pub backward: [u32; 19],
pub sri_pvwv: u32,
}
impl VobuSri {
pub const SIZE: usize = 0x192 - 0xEA;
pub const PACKET_OFFSET: usize = 0xEA;
pub const VALID_BIT: u32 = 0x8000_0000;
pub const INTERMEDIATE_BIT: u32 = 0x4000_0000;
pub const OFFSET_MASK: u32 = 0x3FFF_FFFF;
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("VOBU_SRI: short buffer"));
}
let sri_nvwv = read_u32_be(buf, 0x00)?;
let mut forward = [0u32; 19];
for (i, slot) in forward.iter_mut().enumerate() {
*slot = read_u32_be(buf, 0x04 + i * 4)?;
}
let sri_nv = read_u32_be(buf, 0x50)?; let sri_pv = read_u32_be(buf, 0x54)?; let mut backward = [0u32; 19];
for (i, slot) in backward.iter_mut().enumerate() {
*slot = read_u32_be(buf, 0x58 + i * 4)?;
}
let sri_pvwv = read_u32_be(buf, 0xA4)?; Ok(Self {
sri_nvwv,
forward,
sri_nv,
sri_pv,
backward,
sri_pvwv,
})
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Synci {
pub a_synca: [u16; 8],
pub sp_synca: [u32; 32],
}
impl Synci {
pub const SIZE: usize = 0x222 - 0x192;
pub const PACKET_OFFSET: usize = 0x192;
pub const AUDIO_DIRECTION_BIT: u16 = 0x8000;
pub const SP_DIRECTION_BIT: u32 = 0x8000_0000;
fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::SIZE {
return Err(Error::InvalidUdf("SYNCI: short buffer"));
}
let mut a_synca = [0u16; 8];
for (i, slot) in a_synca.iter_mut().enumerate() {
*slot = read_u16_be(buf, i * 2)?;
}
let mut sp_synca = [0u32; 32];
for (i, slot) in sp_synca.iter_mut().enumerate() {
*slot = read_u32_be(buf, 0x10 + i * 4)?;
}
Ok(Self { a_synca, sp_synca })
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct DsiPacket {
pub general_info: DsiGi,
pub sml_pbi: SmlPbi,
pub sml_agli: SmlAgli,
pub vobu_sri: VobuSri,
pub synci: Synci,
}
impl DsiPacket {
pub const BODY_SIZE: usize = Synci::PACKET_OFFSET + Synci::SIZE;
pub fn nv_pck_scr(&self) -> u32 {
self.general_info.nv_pck_scr
}
pub fn nv_pck_lbn(&self) -> u32 {
self.general_info.nv_pck_lbn
}
pub fn vobu_ea(&self) -> u32 {
self.general_info.vobu_ea
}
pub fn vobu_vob_idn(&self) -> u16 {
self.general_info.vobu_vob_idn
}
pub fn vobu_c_idn(&self) -> u8 {
self.general_info.vobu_c_idn
}
pub fn c_eltm(&self) -> u32 {
self.general_info.c_eltm
}
pub fn cell_elapsed_time(&self) -> PgcTime {
self.general_info.cell_elapsed_time()
}
pub fn cell_elapsed_ns(&self) -> u64 {
self.general_info.cell_elapsed_ns()
}
pub fn parse(buf: &[u8]) -> Result<Self> {
if buf.len() < Self::BODY_SIZE {
return Err(Error::InvalidUdf(
"DSI: shorter than DSI_GI + SML_PBI + SML_AGLI + VOBU_SRI + SYNCI",
));
}
Ok(Self {
general_info: DsiGi::parse(&buf[0x00..])?,
sml_pbi: SmlPbi::parse(&buf[SmlPbi::PACKET_OFFSET..])?,
sml_agli: SmlAgli::parse(&buf[SmlAgli::PACKET_OFFSET..])?,
vobu_sri: VobuSri::parse(&buf[VobuSri::PACKET_OFFSET..])?,
synci: Synci::parse(&buf[Synci::PACKET_OFFSET..])?,
})
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct NavPack {
pub pci: PciPacket,
pub dsi: DsiPacket,
}
impl NavPack {
pub fn parse(sector: &[u8]) -> Result<Self> {
if sector.len() < DVD_SECTOR {
return Err(Error::InvalidUdf("nav-pack: shorter than one sector"));
}
let _pack = PackHeader::parse(sector)?;
if sector[0x0E..0x12] != [0x00, 0x00, 0x01, SC_SYSTEM_HEADER] {
return Err(Error::InvalidUdf(
"nav-pack: 0x000001BB system header missing at offset 0x0E",
));
}
if sector[0x26..0x2A] != [0x00, 0x00, 0x01, SC_PRIVATE_STREAM_2] {
return Err(Error::InvalidUdf(
"nav-pack: 0x000001BF (PCI) missing at offset 0x26",
));
}
if sector[0x2C] != 0x00 {
return Err(Error::InvalidUdf(
"nav-pack: PCI substream-ID != 0x00 at offset 0x2C",
));
}
let pci = PciPacket::parse(§or[0x2D..])?;
if sector[0x400..0x404] != [0x00, 0x00, 0x01, SC_PRIVATE_STREAM_2] {
return Err(Error::InvalidUdf(
"nav-pack: 0x000001BF (DSI) missing at offset 0x400",
));
}
if sector[0x406] != 0x01 {
return Err(Error::InvalidUdf(
"nav-pack: DSI substream-ID != 0x01 at offset 0x406",
));
}
let dsi = DsiPacket::parse(§or[0x407..])?;
Ok(Self { pci, dsi })
}
}
pub fn looks_like_nav_pack(sector: &[u8]) -> bool {
sector.len() >= 0x407
&& sector[0..4] == [0x00, 0x00, 0x01, SC_PACK_HEADER]
&& sector[0x0E..0x12] == [0x00, 0x00, 0x01, SC_SYSTEM_HEADER]
&& sector[0x26..0x2A] == [0x00, 0x00, 0x01, SC_PRIVATE_STREAM_2]
&& sector[0x2C] == 0x00
&& sector[0x400..0x404] == [0x00, 0x00, 0x01, SC_PRIVATE_STREAM_2]
&& sector[0x406] == 0x01
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum ElementaryStream {
Video,
Ac3(u8),
Dts(u8),
Lpcm(u8),
Subpicture(u8),
}
#[derive(Debug, Default, Clone)]
pub struct VobStreams {
pub video: Vec<u8>,
pub ac3: BTreeMap<u8, Vec<u8>>,
pub dts: BTreeMap<u8, Vec<u8>>,
pub lpcm: BTreeMap<u8, Vec<u8>>,
pub subpicture: BTreeMap<u8, Vec<u8>>,
pub nav_packs: Vec<NavPack>,
}
#[derive(Debug, Default, Clone)]
pub struct VobDemuxer {
out: VobStreams,
}
impl VobDemuxer {
pub fn new() -> Self {
Self::default()
}
pub fn take(&mut self) -> VobStreams {
std::mem::take(&mut self.out)
}
pub fn push_sector(&mut self, sector: &[u8]) -> Result<()> {
if sector.len() < DVD_SECTOR {
return Err(Error::InvalidUdf("VOB sector: shorter than 2048 bytes"));
}
if looks_like_nav_pack(sector) {
let nav = NavPack::parse(sector)?;
self.out.nav_packs.push(nav);
return Ok(());
}
let pack = PackHeader::parse(sector)?;
let mut cursor = PackHeader::SIZE + pack.stuffing_bytes as usize;
while cursor + 6 <= sector.len() {
if sector[cursor..cursor + 4] == [0x00, 0x00, 0x01, SC_SYSTEM_HEADER] {
let len = ((sector[cursor + 4] as usize) << 8) | sector[cursor + 5] as usize;
cursor += 6 + len;
continue;
}
if sector[cursor..cursor + 4] == [0x00, 0x00, 0x01, SC_PADDING_STREAM] {
let len = ((sector[cursor + 4] as usize) << 8) | sector[cursor + 5] as usize;
cursor += 6 + len;
continue;
}
if sector[cursor..cursor + 3] != [0x00, 0x00, 0x01] {
break;
}
let pes = PesPacket::parse(§or[cursor..])?;
self.route(&pes);
cursor += pes.wire_size;
}
Ok(())
}
fn route(&mut self, pes: &PesPacket<'_>) {
match pes.stream_id {
0xE0..=0xEF => {
self.out.video.extend_from_slice(pes.payload);
}
SC_PRIVATE_STREAM_1 => {
if let Some(sub) = pes.dvd_substream() {
let body = &pes.payload[1..];
match sub {
DvdSubstream::Ac3(_) => {
self.out
.ac3
.entry(sub.track())
.or_default()
.extend_from_slice(body);
}
DvdSubstream::Dts(_) => {
self.out
.dts
.entry(sub.track())
.or_default()
.extend_from_slice(body);
}
DvdSubstream::Lpcm(_) => {
self.out
.lpcm
.entry(sub.track())
.or_default()
.extend_from_slice(body);
}
DvdSubstream::Subpicture(_) => {
self.out
.subpicture
.entry(sub.track())
.or_default()
.extend_from_slice(body);
}
}
}
}
SC_PRIVATE_STREAM_2 => {}
0xC0..=0xC7 => {
let track = pes.stream_id - 0xC0;
self.out
.ac3
.entry(track)
.or_default()
.extend_from_slice(pes.payload);
}
_ => {}
}
}
pub fn demux_range<R: Read + Seek>(
&mut self,
reader: &mut R,
start: u32,
count: u32,
) -> Result<()> {
let sector_sz = DVD_SECTOR as u64;
reader.seek(SeekFrom::Start(u64::from(start) * sector_sz))?;
let mut buf = vec![0u8; DVD_SECTOR];
for _ in 0..count {
reader.read_exact(&mut buf)?;
self.push_sector(&buf)?;
}
Ok(())
}
}
pub type VobId = u16;
pub type CellId = u8;
pub fn demux_vobs<R: Read + Seek>(
reader: &mut R,
disc: &DvdDisc,
title_set: u8,
cells: &[(VobId, CellId)],
) -> Result<VobStreams> {
if title_set == 0 {
return Err(Error::NotDvdVideo(
"demux_vobs: title_set 0 (VMG) not supported",
));
}
if disc.vtsi(title_set).is_none() {
return Err(Error::NotDvdVideo(
"demux_vobs: title_set has no VTS_xx_0.IFO",
));
}
let first_title_vob = disc
.video_ts_files
.iter()
.find(|f| matches!(f.kind, DvdFileKind::VtsTitle { ts, vob: 1 } if ts == title_set))
.ok_or(Error::NotDvdVideo(
"demux_vobs: title_set has no VTS_xx_1.VOB",
))?;
let base_lba = first_title_vob.lba;
let mut demuxer = VobDemuxer::new();
for &(vob_id, cell_id) in cells {
let (start_rel, end_rel) = lookup_cell(disc, title_set, vob_id, cell_id, reader)?;
let start_abs = base_lba.saturating_add(start_rel).saturating_sub(1);
let count = end_rel.saturating_sub(start_rel).saturating_add(1);
demuxer.demux_range(reader, start_abs, count)?;
}
Ok(demuxer.take())
}
fn lookup_cell<R: Read + Seek>(
disc: &DvdDisc,
title_set: u8,
vob_id: VobId,
cell_id: CellId,
reader: &mut R,
) -> Result<(u32, u32)> {
let vts = disc.parse_vts(reader, title_set)?;
vts.cell_adt
.lookup(vob_id, cell_id)
.ok_or(Error::NotDvdVideo(
"demux_vobs: (vob_id, cell_id) not in C_ADT",
))
}
pub fn demux_vobs_path(
disc: &DvdDisc,
title_set: u8,
cells: &[(VobId, CellId)],
image_path: impl AsRef<Path>,
) -> Result<VobStreams> {
let mut f = File::open(image_path.as_ref())?;
demux_vobs(&mut f, disc, title_set, cells)
}
fn read_u32_be(buf: &[u8], off: usize) -> Result<u32> {
buf.get(off..off + 4)
.and_then(|s| s.try_into().ok())
.map(u32::from_be_bytes)
.ok_or(Error::InvalidUdf("VOB: u32 read out of range"))
}
fn read_u16_be(buf: &[u8], off: usize) -> Result<u16> {
buf.get(off..off + 2)
.and_then(|s| s.try_into().ok())
.map(u16::from_be_bytes)
.ok_or(Error::InvalidUdf("VOB: u16 read out of range"))
}
#[cfg(test)]
mod tests {
use super::*;
fn build_pack_header(scr_base: u64, scr_ext: u16, mux_rate: u32, stuffing: u8) -> [u8; 14] {
let mut b = [0u8; 14];
b[0] = 0x00;
b[1] = 0x00;
b[2] = 0x01;
b[3] = SC_PACK_HEADER;
let scr_high = ((scr_base >> 30) & 0b111) as u8;
b[4] = 0b0100_0000 | (scr_high << 3) | 0b0000_0100;
let scr_mid = ((scr_base >> 15) & 0x7FFF) as u32;
b[4] |= ((scr_mid >> 13) & 0b11) as u8;
b[5] = ((scr_mid >> 5) & 0xFF) as u8;
b[6] = (((scr_mid & 0b1_1111) as u8) << 3) | 0b0000_0100;
let scr_low = (scr_base & 0x7FFF) as u32;
b[6] |= ((scr_low >> 13) & 0b11) as u8;
b[7] = ((scr_low >> 5) & 0xFF) as u8;
b[8] = (((scr_low & 0b1_1111) as u8) << 3) | 0b0000_0100;
b[8] |= ((scr_ext >> 7) & 0b11) as u8;
b[9] = (((scr_ext as u8) & 0x7F) << 1) | 0b0000_0001;
let mr = mux_rate & 0x3F_FFFF;
b[10] = ((mr >> 14) & 0xFF) as u8;
b[11] = ((mr >> 6) & 0xFF) as u8;
b[12] = (((mr & 0x3F) as u8) << 2) | 0b0000_0011;
b[13] = 0xF8 | (stuffing & 0x07);
b
}
fn build_system_header() -> Vec<u8> {
let mut v = vec![0x00, 0x00, 0x01, SC_SYSTEM_HEADER, 0x00, 0x12];
v.extend(std::iter::repeat(0u8).take(18));
v
}
fn build_pes_video(payload: &[u8], pts: Option<u64>) -> Vec<u8> {
let pts_flag = if pts.is_some() { 0b10 } else { 0b00 };
let pts_extra = if pts.is_some() { 5 } else { 0 };
let header_data_len = pts_extra;
let pes_len = 3 + header_data_len + payload.len();
let mut v = Vec::with_capacity(6 + pes_len);
v.extend_from_slice(&[0x00, 0x00, 0x01, 0xE0]);
v.push((pes_len >> 8) as u8);
v.push((pes_len & 0xFF) as u8);
v.push(0b1000_0000); v.push(pts_flag << 6);
v.push(header_data_len as u8);
if let Some(p) = pts {
v.push(0x20 | (((p >> 29) & 0xE) as u8) | 1);
v.push(((p >> 22) & 0xFF) as u8);
v.push((((p >> 14) & 0xFE) as u8) | 1);
v.push(((p >> 7) & 0xFF) as u8);
v.push((((p << 1) & 0xFE) as u8) | 1);
}
v.extend_from_slice(payload);
v
}
fn build_pes_private1(substream: u8, payload: &[u8]) -> Vec<u8> {
let header_data_len = 0;
let mut body = vec![substream];
body.extend_from_slice(payload);
let pes_len = 3 + header_data_len + body.len();
let mut v = Vec::with_capacity(6 + pes_len);
v.extend_from_slice(&[0x00, 0x00, 0x01, SC_PRIVATE_STREAM_1]);
v.push((pes_len >> 8) as u8);
v.push((pes_len & 0xFF) as u8);
v.push(0b1000_0000);
v.push(0b0000_0000); v.push(header_data_len as u8);
v.extend_from_slice(&body);
v
}
#[test]
fn pack_header_parse_roundtrip() {
let hdr = build_pack_header(0x1_2345_6789, 42, 25200, 4);
let p = PackHeader::parse(&hdr).unwrap();
assert_eq!(p.scr_base, 0x1_2345_6789);
assert_eq!(p.scr_ext, 42);
assert_eq!(p.mux_rate, 25200);
assert_eq!(p.stuffing_bytes, 4);
}
#[test]
fn pack_header_rejects_bad_sync() {
let mut hdr = build_pack_header(0, 0, 1, 0);
hdr[3] = 0xBB; assert!(PackHeader::parse(&hdr).is_err());
}
#[test]
fn pack_header_rejects_zero_mux_rate() {
let hdr = build_pack_header(0, 0, 0, 0);
let err = PackHeader::parse(&hdr).unwrap_err();
matches!(err, Error::InvalidUdf(_));
}
#[test]
fn pes_no_pts() {
let bytes = build_pes_video(&[0xAA, 0xBB, 0xCC], None);
let pes = PesPacket::parse(&bytes).unwrap();
assert_eq!(pes.stream_id, 0xE0);
assert_eq!(pes.pts, None);
assert_eq!(pes.dts, None);
assert_eq!(pes.payload, &[0xAA, 0xBB, 0xCC]);
assert_eq!(pes.wire_size, bytes.len());
}
#[test]
fn pes_with_pts() {
let pts = 0x1_2345_6789;
let bytes = build_pes_video(&[0x10, 0x20], Some(pts));
let pes = PesPacket::parse(&bytes).unwrap();
assert_eq!(pes.pts, Some(pts));
assert_eq!(pes.dts, None);
assert_eq!(pes.payload, &[0x10, 0x20]);
}
#[test]
fn pes_rejects_bad_start_code() {
let mut bytes = build_pes_video(&[0xAA], None);
bytes[2] = 0x02;
assert!(PesPacket::parse(&bytes).is_err());
}
#[test]
fn substream_classifies_ac3_track() {
let pes_bytes = build_pes_private1(0x82, &[0x0B, 0x77]); let pes = PesPacket::parse(&pes_bytes).unwrap();
let sub = pes.dvd_substream().unwrap();
assert_eq!(sub, DvdSubstream::Ac3(0x82));
assert_eq!(sub.track(), 2);
}
#[test]
fn substream_classifies_subpicture() {
let pes_bytes = build_pes_private1(0x21, &[0x00]);
let sub = PesPacket::parse(&pes_bytes)
.unwrap()
.dvd_substream()
.unwrap();
assert_eq!(sub, DvdSubstream::Subpicture(0x21));
assert_eq!(sub.track(), 1);
}
#[test]
fn substream_classifies_lpcm_and_dts() {
let lpcm = build_pes_private1(0xA0, &[0]);
let dts = build_pes_private1(0x88, &[0x7F, 0xFE, 0x80, 0x01]);
let lpcm_sub = PesPacket::parse(&lpcm).unwrap().dvd_substream().unwrap();
let dts_sub = PesPacket::parse(&dts).unwrap().dvd_substream().unwrap();
assert_eq!(lpcm_sub, DvdSubstream::Lpcm(0xA0));
assert_eq!(dts_sub, DvdSubstream::Dts(0x88));
assert_eq!(lpcm_sub.track(), 0);
assert_eq!(dts_sub.track(), 0);
}
fn build_nav_sector(nv_lbn: u32, vobu_s_ptm: u32, vobu_ea: u32) -> Vec<u8> {
let mut sector = vec![0u8; DVD_SECTOR];
let pack = build_pack_header(0, 0, 25200, 0);
sector[..14].copy_from_slice(&pack);
let sys = build_system_header();
sector[0x0E..0x0E + sys.len()].copy_from_slice(&sys);
sector[0x26] = 0x00;
sector[0x27] = 0x00;
sector[0x28] = 0x01;
sector[0x29] = SC_PRIVATE_STREAM_2;
sector[0x2A] = 0x03;
sector[0x2B] = 0xD4;
sector[0x2C] = 0x00; sector[0x2D..0x31].copy_from_slice(&nv_lbn.to_be_bytes());
sector[0x39..0x3D].copy_from_slice(&vobu_s_ptm.to_be_bytes());
sector[0x400] = 0x00;
sector[0x401] = 0x00;
sector[0x402] = 0x01;
sector[0x403] = SC_PRIVATE_STREAM_2;
sector[0x404] = 0x03;
sector[0x405] = 0xFA;
sector[0x406] = 0x01; sector[0x40B..0x40F].copy_from_slice(&nv_lbn.to_be_bytes()); sector[0x40F..0x413].copy_from_slice(&vobu_ea.to_be_bytes()); sector
}
#[test]
fn nav_pack_parse() {
let sector = build_nav_sector(0xDEAD_BEEF, 0x0001_2345, 0x0000_07FF);
assert!(looks_like_nav_pack(§or));
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.nv_pck_lbn, 0xDEAD_BEEF);
assert_eq!(nav.pci.vobu_s_ptm, 0x0001_2345);
assert_eq!(nav.dsi.general_info.nv_pck_lbn, 0xDEAD_BEEF);
assert_eq!(nav.dsi.general_info.vobu_ea, 0x0000_07FF);
assert_eq!(nav.dsi.nv_pck_lbn(), 0xDEAD_BEEF);
assert_eq!(nav.dsi.vobu_ea(), 0x0000_07FF);
}
#[test]
fn nav_pack_rejects_missing_dsi() {
let mut sector = build_nav_sector(1, 0, 0);
sector[0x403] = 0xCC; assert!(NavPack::parse(§or).is_err());
assert!(!looks_like_nav_pack(§or));
}
fn pci(p: usize) -> usize {
0x2D + p
}
#[test]
fn pci_default_nsml_agli_is_empty() {
let sector = build_nav_sector(1, 0, 0);
let nav = NavPack::parse(§or).unwrap();
assert!(nav.pci.nsml_agli.is_empty());
assert_eq!(nav.pci.nsml_agli.active_angle_count(), 0);
for c in &nav.pci.nsml_agli.cells {
assert!(c.is_absent());
assert!(!c.is_no_more_video());
assert_eq!(c.offset_sectors(), None);
}
}
#[test]
fn pci_parses_nsml_agli_block() {
let mut sector = build_nav_sector(1, 0, 0);
sector[pci(0x3C)..pci(0x40)].copy_from_slice(&0x0000_0100u32.to_be_bytes());
sector[pci(0x40)..pci(0x44)].copy_from_slice(&0x8000_0080u32.to_be_bytes());
sector[pci(0x44)..pci(0x48)].copy_from_slice(&NsmlAngleCell::NO_MORE_VIDEO.to_be_bytes());
sector[pci(0x5C)..pci(0x60)].copy_from_slice(&0x0000_0009u32.to_be_bytes());
let nav = NavPack::parse(§or).unwrap();
let agli = &nav.pci.nsml_agli;
assert!(!agli.is_empty());
let a1 = agli.angle(1).unwrap();
assert_eq!(a1.dsta, 0x0000_0100);
assert!(!a1.is_backward());
assert!(!a1.is_absent());
assert_eq!(a1.offset_sectors(), Some(0x100));
let a2 = agli.angle(2).unwrap();
assert!(a2.is_backward());
assert_eq!(a2.offset_sectors(), Some(0x080));
let a3 = agli.angle(3).unwrap();
assert!(a3.is_no_more_video());
assert_eq!(a3.offset_sectors(), None);
assert!(agli.angle(4).unwrap().is_absent());
assert_eq!(agli.angle(9).unwrap().dsta, 0x0000_0009);
assert_eq!(agli.active_angle_count(), 3);
assert_eq!(agli.angle(0), None);
assert_eq!(agli.angle(10), None);
}
#[test]
fn nsml_agli_parse_rejects_short_buffer() {
let buf = [0u8; 0x40];
assert!(NsmlAgli::parse(&buf).is_err());
let buf = [0u8; 0x60];
assert!(NsmlAgli::parse(&buf).is_ok());
}
fn add_one_button_hli(sector: &mut [u8]) {
sector[pci(0x60)] = 0x00;
sector[pci(0x61)] = 0x01;
sector[pci(0x62)..pci(0x66)].copy_from_slice(&0x0000_1111u32.to_be_bytes());
sector[pci(0x66)..pci(0x6A)].copy_from_slice(&0x0000_2222u32.to_be_bytes());
sector[pci(0x6A)..pci(0x6E)].copy_from_slice(&0x0000_3333u32.to_be_bytes());
sector[pci(0x6E)] = 0x01;
sector[pci(0x6F)] = 0x00;
sector[pci(0x70)] = 1; sector[pci(0x71)] = 1; sector[pci(0x72)] = 1; sector[pci(0x74)] = 1; sector[pci(0x75)] = 1;
sector[pci(0x76)..pci(0x7E)]
.copy_from_slice(&[0x21, 0x43, 0x65, 0x87, 0xA9, 0xCB, 0xED, 0x0F]);
let entry: [u8; 18] = [
0x45, 0x61, 0x23, 0x47, 0x82, 0x9A, 0x05, 0x06, 0x07, 0x08, 0xC0, 0xC1, 0xC2, 0xC3,
0xC4, 0xC5, 0xC6, 0xC7,
];
let off = pci(0x8E);
sector[off..off + 18].copy_from_slice(&entry);
}
#[test]
fn pci_without_buttons_yields_no_highlight() {
let sector = build_nav_sector(1, 0, 0);
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.hli_ss, 0);
assert!(nav.pci.highlight.is_none());
}
#[test]
fn pci_decodes_single_button_highlight() {
let mut sector = build_nav_sector(1, 0, 0);
add_one_button_hli(&mut sector);
let nav = NavPack::parse(§or).unwrap();
let hli = nav.pci.highlight.expect("highlight present");
assert_eq!(hli.hli_s_ptm, 0x0000_1111);
assert_eq!(hli.hli_e_ptm, 0x0000_2222);
assert_eq!(hli.btn_sl_e_ptm, 0x0000_3333);
assert_eq!(hli.btn_md, 0x0100);
let bm = hli.button_mode();
assert_eq!(bm.group_count, 0);
assert_eq!(bm.group_types, [1, 0, 0]);
assert_eq!(hli.btn_sn, 1);
assert_eq!(hli.btn_ns, 1);
assert_eq!(hli.nsl_btn_ns, 1);
assert_eq!(hli.fosl_btnn, 1);
assert_eq!(hli.foac_btnn, 1);
let sel = hli.sl_coli[0].selection;
assert_eq!(sel[0].color, 3); assert_eq!(sel[1].color, 4); assert_eq!(sel[2].color, 1); assert_eq!(sel[3].color, 2); assert_eq!(sel[0].contrast, 7);
assert_eq!(sel[1].contrast, 8);
assert_eq!(sel[2].contrast, 5);
assert_eq!(sel[3].contrast, 6);
let act = hli.sl_coli[0].action;
assert_eq!(act[0].color, 0xB);
assert_eq!(act[1].color, 0xC);
assert_eq!(act[2].color, 0x9);
assert_eq!(act[3].color, 0xA);
assert_eq!(hli.buttons.len(), 1);
let b = &hli.buttons[0];
assert_eq!(b.btn_coln, 1);
assert_eq!(b.start_x, 0x56);
assert_eq!(b.end_x, 0x123);
assert_eq!(b.start_y, 0x78);
assert_eq!(b.end_y, 0x29A);
assert!(b.auto_action);
assert_eq!(b.up, 5);
assert_eq!(b.down, 6);
assert_eq!(b.left, 7);
assert_eq!(b.right, 8);
assert_eq!(b.command, [0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7]);
}
#[test]
fn pci_rejects_overlong_btn_ns() {
let mut sector = build_nav_sector(1, 0, 0);
add_one_button_hli(&mut sector);
sector[pci(0x71)] = 37; assert!(NavPack::parse(§or).is_err());
}
#[test]
fn button_mode_decodes_btn_md_subfields() {
let reserved = (1u16 << 15) | (1 << 14) | (1 << 11) | (1 << 7) | (1 << 3);
let btn_md = reserved | (0b10 << 12) | (0b101 << 8) | (0b011 << 4) | 0b110;
let bm = ButtonMode::from_btn_md(btn_md);
assert_eq!(bm.group_count, 2);
assert_eq!(bm.group_types, [5, 3, 6]);
let cleaned = (0b10u16 << 12) | (0b101 << 8) | (0b011 << 4) | 0b110;
assert_eq!(bm.to_btn_md(), cleaned);
assert_eq!(ButtonMode::from_btn_md(cleaned), bm);
}
#[test]
fn button_mode_zero_is_default() {
let bm = ButtonMode::from_btn_md(0);
assert_eq!(bm, ButtonMode::default());
assert_eq!(bm.group_count, 0);
assert_eq!(bm.group_types, [0, 0, 0]);
assert_eq!(bm.to_btn_md(), 0);
}
#[test]
fn highlight_status_maps_two_low_bits_to_named_variants() {
for reserved in [0x0000u16, 0xFFFCu16, 0xA5A4u16] {
assert_eq!(
HighlightStatus::from_hli_ss(reserved),
HighlightStatus::None
);
assert_eq!(
HighlightStatus::from_hli_ss(reserved | 0b01),
HighlightStatus::AllNew
);
assert_eq!(
HighlightStatus::from_hli_ss(reserved | 0b10),
HighlightStatus::UsePrevious
);
assert_eq!(
HighlightStatus::from_hli_ss(reserved | 0b11),
HighlightStatus::UsePreviousExceptCommands
);
}
}
#[test]
fn highlight_status_classifier_predicates_match_spec_table() {
assert!(HighlightStatus::None.is_none());
assert!(!HighlightStatus::AllNew.is_none());
assert!(!HighlightStatus::UsePrevious.is_none());
assert!(!HighlightStatus::UsePreviousExceptCommands.is_none());
assert!(!HighlightStatus::None.declares_new_geometry());
assert!(HighlightStatus::AllNew.declares_new_geometry());
assert!(!HighlightStatus::UsePrevious.declares_new_geometry());
assert!(!HighlightStatus::UsePreviousExceptCommands.declares_new_geometry());
assert!(!HighlightStatus::None.reuses_previous_geometry());
assert!(!HighlightStatus::AllNew.reuses_previous_geometry());
assert!(HighlightStatus::UsePrevious.reuses_previous_geometry());
assert!(HighlightStatus::UsePreviousExceptCommands.reuses_previous_geometry());
assert!(!HighlightStatus::None.supplies_own_commands());
assert!(HighlightStatus::AllNew.supplies_own_commands());
assert!(!HighlightStatus::UsePrevious.supplies_own_commands());
assert!(HighlightStatus::UsePreviousExceptCommands.supplies_own_commands());
}
#[test]
fn highlight_status_to_bits_round_trips_through_from_hli_ss() {
for status in [
HighlightStatus::None,
HighlightStatus::AllNew,
HighlightStatus::UsePrevious,
HighlightStatus::UsePreviousExceptCommands,
] {
assert_eq!(HighlightStatus::from_hli_ss(status.to_bits()), status);
}
for raw in 0u16..=0xFFFF {
let status = HighlightStatus::from_hli_ss(raw);
assert_eq!(status.to_bits(), raw & 0b11);
}
}
#[test]
fn pci_highlight_status_accessor_matches_raw_word() {
let sector = build_nav_sector(1, 0, 0);
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.hli_ss & 0b11, 0);
assert_eq!(nav.pci.highlight_status(), HighlightStatus::None);
let mut sector = build_nav_sector(1, 0, 0);
add_one_button_hli(&mut sector);
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.hli_ss, 0x0001);
assert_eq!(nav.pci.highlight_status(), HighlightStatus::AllNew);
assert!(nav.pci.highlight_status().declares_new_geometry());
assert!(nav.pci.highlight_status().supplies_own_commands());
let mut sector = build_nav_sector(1, 0, 0);
sector[pci(0x60)] = 0x00;
sector[pci(0x61)] = 0x02;
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.hli_ss, 0x0002);
assert_eq!(nav.pci.highlight_status(), HighlightStatus::UsePrevious);
assert!(nav.pci.highlight_status().reuses_previous_geometry());
assert!(!nav.pci.highlight_status().supplies_own_commands());
assert!(nav.pci.highlight.is_none());
let mut sector = build_nav_sector(1, 0, 0);
sector[pci(0x60)] = 0x00;
sector[pci(0x61)] = 0x03;
let nav = NavPack::parse(§or).unwrap();
assert_eq!(nav.pci.hli_ss, 0x0003);
assert_eq!(
nav.pci.highlight_status(),
HighlightStatus::UsePreviousExceptCommands
);
assert!(nav.pci.highlight_status().reuses_previous_geometry());
assert!(nav.pci.highlight_status().supplies_own_commands());
}
#[test]
fn pci_rejects_truncated_button_table() {
let mut buf = vec![0u8; PciPacket::BTN_IT + 18]; buf[0x71] = 36; assert!(PciPacket::parse(&buf).is_err());
}
#[test]
fn vobu_demux_routes_video_and_audio() {
let mut demuxer = VobDemuxer::new();
let nav = build_nav_sector(100, 0x12345, 0x000003);
demuxer.push_sector(&nav).unwrap();
assert_eq!(demuxer.out.nav_packs.len(), 1);
let mut sec1 = vec![0u8; DVD_SECTOR];
let pack = build_pack_header(0, 0, 25200, 0);
sec1[..14].copy_from_slice(&pack);
let video_payload: Vec<u8> = (1..=16).collect();
let video_pes = build_pes_video(&video_payload, Some(0x1000));
sec1[14..14 + video_pes.len()].copy_from_slice(&video_pes);
demuxer.push_sector(&sec1).unwrap();
let mut sec2 = vec![0u8; DVD_SECTOR];
sec2[..14].copy_from_slice(&pack);
let ac3_payload: Vec<u8> = vec![0xAA; 32];
let ac3_pes = build_pes_private1(0x81, &ac3_payload);
sec2[14..14 + ac3_pes.len()].copy_from_slice(&ac3_pes);
demuxer.push_sector(&sec2).unwrap();
let streams = demuxer.take();
assert_eq!(streams.video, video_payload);
assert_eq!(
streams.ac3.get(&1).map(Vec::as_slice),
Some(ac3_payload.as_slice())
);
assert_eq!(streams.nav_packs.len(), 1);
assert_eq!(streams.nav_packs[0].pci.nv_pck_lbn, 100);
}
#[test]
fn dsi_section_offsets_match_spec() {
assert_eq!(DsiGi::SIZE, 0x20);
assert_eq!(SmlPbi::PACKET_OFFSET, 0x20);
assert_eq!(SmlPbi::SIZE, 0xB4 - 0x20);
assert_eq!(SmlPbi::PACKET_OFFSET + SmlPbi::SIZE, SmlAgli::PACKET_OFFSET);
assert_eq!(SmlAgli::PACKET_OFFSET, 0xB4);
assert_eq!(SmlAgli::SIZE, 0xEA - 0xB4);
assert_eq!(
SmlAgli::PACKET_OFFSET + SmlAgli::SIZE,
VobuSri::PACKET_OFFSET
);
assert_eq!(VobuSri::PACKET_OFFSET, 0xEA);
assert_eq!(VobuSri::SIZE, 0x192 - 0xEA);
assert_eq!(VobuSri::SIZE, 42 * 4);
assert_eq!(VobuSri::PACKET_OFFSET + VobuSri::SIZE, Synci::PACKET_OFFSET);
assert_eq!(Synci::PACKET_OFFSET, 0x192);
assert_eq!(Synci::SIZE, 0x222 - 0x192);
assert_eq!(Synci::SIZE, 8 * 2 + 32 * 4);
assert_eq!(DsiPacket::BODY_SIZE, 0x222);
}
fn build_dsi_body() -> Vec<u8> {
let mut buf = vec![0u8; DsiPacket::BODY_SIZE];
buf[0x00..0x04].copy_from_slice(&0x1111_2222u32.to_be_bytes()); buf[0x04..0x08].copy_from_slice(&0x3333_4444u32.to_be_bytes()); buf[0x08..0x0C].copy_from_slice(&0x5555_6666u32.to_be_bytes()); buf[0x0C..0x10].copy_from_slice(&0x7777_8888u32.to_be_bytes()); buf[0x10..0x14].copy_from_slice(&0x9999_AAAAu32.to_be_bytes()); buf[0x14..0x18].copy_from_slice(&0xBBBB_CCCCu32.to_be_bytes()); buf[0x18..0x1A].copy_from_slice(&0xDEADu16.to_be_bytes()); buf[0x1A] = 0x00; buf[0x1B] = 0x42; buf[0x1C..0x20].copy_from_slice(&0xC0DE_F00Du32.to_be_bytes());
buf[0x20..0x22].copy_from_slice(&0xF000u16.to_be_bytes());
buf[0x22..0x26].copy_from_slice(&0x0123_4567u32.to_be_bytes()); buf[0x26..0x2A].copy_from_slice(&0x89AB_CDEFu32.to_be_bytes()); buf[0x2A..0x2C].copy_from_slice(&0xFFFFu16.to_be_bytes()); buf[0x2C..0x30].copy_from_slice(&0x0000_AAAAu32.to_be_bytes()); buf[0x30..0x34].copy_from_slice(&0x0000_BBBBu32.to_be_bytes()); for stream in 0..8u32 {
let base = 0x34 + stream as usize * 16;
buf[base..base + 4].copy_from_slice(&(0x1000_0000 + stream).to_be_bytes());
buf[base + 4..base + 8].copy_from_slice(&(0x2000_0000 + stream).to_be_bytes());
buf[base + 8..base + 12].copy_from_slice(&(0x3000_0000 + stream).to_be_bytes());
buf[base + 12..base + 16].copy_from_slice(&(0x4000_0000 + stream).to_be_bytes());
}
for cell in 0..9u32 {
let base = 0xB4 + cell as usize * 6;
buf[base..base + 4].copy_from_slice(&(0x5000_0000 + cell).to_be_bytes());
buf[base + 4..base + 6].copy_from_slice(&(0x6000u16 + cell as u16).to_be_bytes());
}
buf[0xEA..0xEE].copy_from_slice(&0x8000_0001u32.to_be_bytes()); for i in 0..19u32 {
let off = 0xEE + i as usize * 4;
buf[off..off + 4].copy_from_slice(&(0x9000_0000 + i).to_be_bytes());
}
buf[0x13A..0x13E].copy_from_slice(&0x8000_0002u32.to_be_bytes()); buf[0x13E..0x142].copy_from_slice(&0x8000_0003u32.to_be_bytes()); for i in 0..19u32 {
let off = 0x142 + i as usize * 4;
buf[off..off + 4].copy_from_slice(&(0xA000_0000 + i).to_be_bytes());
}
buf[0x18E..0x192].copy_from_slice(&0x8000_0004u32.to_be_bytes());
for i in 0..8u16 {
let off = 0x192 + i as usize * 2;
buf[off..off + 2].copy_from_slice(&(0x7000u16 + i).to_be_bytes());
}
for i in 0..32u32 {
let off = 0x1A2 + i as usize * 4;
buf[off..off + 4].copy_from_slice(&(0xB000_0000 + i).to_be_bytes());
}
buf
}
#[test]
fn dsi_parses_general_info_block() {
let buf = build_dsi_body();
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let gi = dsi.general_info;
assert_eq!(gi.nv_pck_scr, 0x1111_2222);
assert_eq!(gi.nv_pck_lbn, 0x3333_4444);
assert_eq!(gi.vobu_ea, 0x5555_6666);
assert_eq!(gi.vobu_1stref_ea, 0x7777_8888);
assert_eq!(gi.vobu_2ndref_ea, 0x9999_AAAA);
assert_eq!(gi.vobu_3rdref_ea, 0xBBBB_CCCC);
assert_eq!(gi.vobu_vob_idn, 0xDEAD);
assert_eq!(gi.vobu_c_idn, 0x42);
assert_eq!(gi.c_eltm, 0xC0DE_F00D);
assert_eq!(dsi.nv_pck_scr(), 0x1111_2222);
assert_eq!(dsi.nv_pck_lbn(), 0x3333_4444);
assert_eq!(dsi.vobu_ea(), 0x5555_6666);
assert_eq!(dsi.vobu_vob_idn(), 0xDEAD);
assert_eq!(dsi.vobu_c_idn(), 0x42);
assert_eq!(dsi.c_eltm(), 0xC0DE_F00D);
}
#[test]
fn dsi_parses_sml_pbi_block_and_ilvu_flags() {
let buf = build_dsi_body();
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let pbi = dsi.sml_pbi;
assert_eq!(pbi.ilvu, 0xF000);
assert!(pbi.preu());
assert!(pbi.is_ilvu());
assert!(pbi.unit_start());
assert!(pbi.unit_end());
assert_eq!(pbi.ilvu_ea, 0x0123_4567);
assert_eq!(pbi.nxt_ilvu_sa, 0x89AB_CDEF);
assert_eq!(pbi.nxt_ilvu_sz, 0xFFFF);
assert_eq!(pbi.vob_v_s_ptm, 0x0000_AAAA);
assert_eq!(pbi.vob_v_e_ptm, 0x0000_BBBB);
for (i, gap) in pbi.audio_gaps.iter().enumerate() {
let i = i as u32;
assert_eq!(gap.stp_ptm1, 0x1000_0000 + i);
assert_eq!(gap.stp_ptm2, 0x2000_0000 + i);
assert_eq!(gap.gap_len1, 0x3000_0000 + i);
assert_eq!(gap.gap_len2, 0x4000_0000 + i);
}
}
#[test]
fn dsi_pbi_ilvu_flag_decoders_isolate_bits() {
let bits = [
(0x8000u16, SmlPbi::preu as fn(&SmlPbi) -> bool),
(0x4000, SmlPbi::is_ilvu),
(0x2000, SmlPbi::unit_start),
(0x1000, SmlPbi::unit_end),
];
for (bit, getter) in bits {
let mut buf = vec![0u8; SmlPbi::SIZE];
buf[0..2].copy_from_slice(&bit.to_be_bytes());
let pbi = SmlPbi::parse(&buf).expect("SmlPbi parses");
assert!(getter(&pbi), "bit {:#06x} should be set", bit);
for (other_bit, other_getter) in bits.iter().filter(|(b, _)| *b != bit) {
assert!(
!other_getter(&pbi),
"bit {:#06x} leaked into bit {:#06x}",
bit,
other_bit
);
}
}
}
#[test]
fn dsi_parses_sml_agli_block() {
let buf = build_dsi_body();
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
for (i, cell) in dsi.sml_agli.cells.iter().enumerate() {
let i = i as u32;
assert_eq!(cell.dsta, 0x5000_0000 + i);
assert_eq!(cell.sz, 0x6000 + i as u16);
}
assert_eq!(SmlAgli::PACKET_OFFSET + SmlAgli::SIZE, 0xEA);
}
#[test]
fn dsi_parses_vobu_sri_block_and_brackets() {
let buf = build_dsi_body();
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let sri = dsi.vobu_sri;
assert_eq!(sri.sri_nvwv, 0x8000_0001);
for (i, slot) in sri.forward.iter().enumerate() {
assert_eq!(*slot, 0x9000_0000 + i as u32);
}
assert_eq!(sri.sri_nv, 0x8000_0002);
assert_eq!(sri.sri_pv, 0x8000_0003);
for (i, slot) in sri.backward.iter().enumerate() {
assert_eq!(*slot, 0xA000_0000 + i as u32);
}
assert_eq!(sri.sri_pvwv, 0x8000_0004);
assert_eq!(VobuSri::VALID_BIT, 0x8000_0000);
assert_eq!(VobuSri::INTERMEDIATE_BIT, 0x4000_0000);
assert_eq!(VobuSri::OFFSET_MASK, 0x3FFF_FFFF);
assert!(sri.sri_nvwv & VobuSri::VALID_BIT != 0);
assert_eq!(sri.sri_nvwv & VobuSri::OFFSET_MASK, 1);
}
#[test]
fn dsi_parses_synci_block() {
let buf = build_dsi_body();
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let sy = dsi.synci;
for (i, a) in sy.a_synca.iter().enumerate() {
assert_eq!(*a, 0x7000 + i as u16);
}
for (i, sp) in sy.sp_synca.iter().enumerate() {
assert_eq!(*sp, 0xB000_0000 + i as u32);
}
assert_eq!(Synci::AUDIO_DIRECTION_BIT, 0x8000);
assert_eq!(Synci::SP_DIRECTION_BIT, 0x8000_0000);
}
#[test]
fn dsi_rejects_short_buffer() {
let short = vec![0u8; DsiPacket::BODY_SIZE - 1];
assert!(DsiPacket::parse(&short).is_err());
}
#[test]
fn dsi_gi_cell_elapsed_time_decodes_bcd() {
use crate::ifo::FrameRate;
let mut buf = build_dsi_body();
let c_eltm_be = [0x00u8, 0x01, 0x23, 0xD0];
buf[0x1C..0x20].copy_from_slice(&c_eltm_be);
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let t = dsi.general_info.cell_elapsed_time();
assert_eq!(t.hours, 0);
assert_eq!(t.minutes, 1);
assert_eq!(t.seconds, 23);
assert_eq!(t.frames, 10);
assert_eq!(t.frame_rate, FrameRate::Ntsc30);
assert_eq!(dsi.general_info.cell_elapsed_ns(), 83_333_333_333);
assert_eq!(dsi.cell_elapsed_time(), t);
assert_eq!(dsi.cell_elapsed_ns(), 83_333_333_333);
}
#[test]
fn dsi_gi_cell_elapsed_time_pal_round_trip() {
use crate::ifo::FrameRate;
let mut buf = build_dsi_body();
buf[0x1C..0x20].copy_from_slice(&[0x01u8, 0x00, 0x00, 0x60]);
let dsi = DsiPacket::parse(&buf).expect("DSI body parses");
let t = dsi.cell_elapsed_time();
assert_eq!(t.frame_rate, FrameRate::Pal25);
assert_eq!(t.frames, 20);
assert_eq!(t.total_seconds(), 3600);
assert_eq!(dsi.cell_elapsed_ns(), 3_600_800_000_000);
}
#[test]
fn dsi_nav_pack_round_trip_through_full_sector() {
let body = build_dsi_body();
let mut sector = build_nav_sector(1, 0, 0);
sector[0x407..0x407 + body.len()].copy_from_slice(&body);
let nav = NavPack::parse(§or).expect("nav pack parses");
assert_eq!(nav.dsi.general_info.nv_pck_lbn, 0x3333_4444);
assert_eq!(nav.dsi.sml_pbi.ilvu, 0xF000);
assert_eq!(nav.dsi.sml_agli.cells[0].dsta, 0x5000_0000);
assert_eq!(nav.dsi.vobu_sri.sri_nvwv, 0x8000_0001);
assert_eq!(nav.dsi.synci.a_synca[0], 0x7000);
}
}