use std::io::{Read, Seek, SeekFrom};
use crate::sector::SectorMode;
use crate::IsoError;
const SIGNATURE: &[u8; 16] = b"MEDIA DESCRIPTOR";
const HEADER_LEN: usize = 88;
const SESSION_BLOCK_LEN: usize = 24;
const TRACK_BLOCK_LEN: usize = 80;
const MAX_BLOCKS: usize = 4096;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsTrack {
pub point: u8,
pub mode: u8,
pub subchannel: u8,
pub sector_size: u16,
pub start_sector: u32,
pub start_offset: u64,
pub num_sectors: u32,
}
impl MdsTrack {
#[must_use]
pub fn sector_mode(&self) -> Option<SectorMode> {
sector_mode_for(self.mode, self.sector_size)
}
#[must_use]
pub fn is_data(&self) -> bool {
self.sector_mode().is_some()
}
#[must_use]
pub fn data_size(&self) -> u64 {
u64::from(self.num_sectors) * u64::from(self.sector_size)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsImage {
pub medium_type: u16,
pub tracks: Vec<MdsTrack>,
}
impl MdsImage {
#[must_use]
pub fn track_count(&self) -> usize {
self.tracks.len()
}
#[must_use]
pub fn data_track(&self) -> Option<&MdsTrack> {
self.tracks.iter().find(|t| t.is_data())
}
}
enum TrackKind {
Audio,
Mode1,
Mode2,
}
fn track_kind(mode: u8) -> TrackKind {
match mode {
0xA9 | 0xE9 => TrackKind::Audio,
0xAA | 0xEA | 0x02 => TrackKind::Mode1, 0xAB | 0xAC | 0xEC | 0xAD | 0xED => TrackKind::Mode2, other => match other & 0x07 {
1 => TrackKind::Audio,
2 => TrackKind::Mode1,
_ => TrackKind::Mode2, },
}
}
#[must_use]
pub fn sector_mode_for(mode: u8, sector_size: u16) -> Option<SectorMode> {
let kind = track_kind(mode);
match sector_size {
2048 => match kind {
TrackKind::Audio => None,
_ => Some(SectorMode::Iso2048),
},
2336 => Some(SectorMode::Mode2_2336),
2352 => match kind {
TrackKind::Audio => None,
TrackKind::Mode1 => Some(SectorMode::Raw2352),
TrackKind::Mode2 => Some(SectorMode::Raw2352Mode2),
},
2448 => match kind {
TrackKind::Audio => None,
TrackKind::Mode1 => Some(SectorMode::Raw2448),
TrackKind::Mode2 => Some(SectorMode::Raw2448Mode2),
},
_ => None,
}
}
pub fn parse<R: Read + Seek>(reader: &mut R) -> Result<MdsImage, IsoError> {
let file_size = reader.seek(SeekFrom::End(0))?;
let header = read_at(reader, 0, HEADER_LEN)?;
if &header[0..16] != SIGNATURE {
return Err(IsoError::NotAnIso);
}
let medium_type = le16(&header[18..20]);
let num_sessions = le16(&header[20..22]) as usize;
let sessions_offset = u64::from(le32(&header[80..84]));
let mut tracks = Vec::new();
for s in 0..num_sessions.min(MAX_BLOCKS) {
let off = sessions_offset + (s * SESSION_BLOCK_LEN) as u64;
let Ok(session) = read_at(reader, off, SESSION_BLOCK_LEN) else {
break;
};
let num_all_blocks = session[10] as usize;
let tracks_offset = u64::from(le32(&session[20..24]));
for b in 0..num_all_blocks.min(MAX_BLOCKS) {
let toff = tracks_offset + (b * TRACK_BLOCK_LEN) as u64;
let Ok(tb) = read_at(reader, toff, TRACK_BLOCK_LEN) else {
break;
};
let point = tb[4];
if !(1..=99).contains(&point) {
continue; }
let extra_offset = u64::from(le32(&tb[12..16]));
let num_sectors = read_extra_length(reader, extra_offset, file_size);
tracks.push(MdsTrack {
point,
mode: tb[0],
subchannel: tb[1],
sector_size: le16(&tb[16..18]),
start_sector: le32(&tb[36..40]),
start_offset: le64(&tb[40..48]),
num_sectors,
});
}
}
Ok(MdsImage { medium_type, tracks })
}
fn read_extra_length<R: Read + Seek>(reader: &mut R, offset: u64, file_size: u64) -> u32 {
if offset == 0 || offset + 8 > file_size {
return 0;
}
match read_at(reader, offset, 8) {
Ok(extra) => le32(&extra[4..8]), Err(_) => 0,
}
}
fn read_at<R: Read + Seek>(reader: &mut R, offset: u64, len: usize) -> Result<Vec<u8>, IsoError> {
reader.seek(SeekFrom::Start(offset))?;
let mut buf = vec![0u8; len];
reader.read_exact(&mut buf)?;
Ok(buf)
}
fn le16(b: &[u8]) -> u16 {
u16::from_le_bytes([b[0], b[1]])
}
fn le32(b: &[u8]) -> u32 {
u32::from_le_bytes([b[0], b[1], b[2], b[3]])
}
fn le64(b: &[u8]) -> u64 {
u64::from_le_bytes(b[0..8].try_into().unwrap())
}