use crate::error::DiscError;
use crate::types::{CdSubcodeType, CdToc, CdTrack, CdTrackType, DiscType, Msf};
use std::fs;
use std::path::Path;
pub const MDS_MAGIC: &[u8; 16] = b"MEDIA DESCRIPTOR";
pub const MDS_HEADER_MIN_SIZE: usize = 80;
pub const MDS_SESSION_BLOCK_SIZE: usize = 24;
pub const MDS_TRACK_BLOCK_SIZE: usize = 80;
pub const MDS_MEDIUM_CD: u16 = 0x0000;
pub const MDS_MEDIUM_CD_R: u16 = 0x0001;
pub const MDS_MEDIUM_CD_RW: u16 = 0x0002;
pub const MDS_MEDIUM_DVD: u16 = 0x0010;
pub const MDS_MEDIUM_DVD_PLUS_R_DL: u16 = 0x0016;
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsHeader {
pub version_major: u8,
pub version_minor: u8,
pub medium_type: u16,
pub num_sessions: u16,
pub sessions_offset: u32,
pub dpm_offset: u32,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsSession {
pub session_start_lba: i32,
pub session_end_lba: i32,
pub session_number: u16,
pub num_all_tracks: u8,
pub num_leadout_tracks: u8,
pub first_track_num: u16,
pub last_track_num: u16,
pub track_data_offset: u32,
pub tracks: Vec<MdsTrack>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsTrack {
pub mode: u8,
pub subchannel_mode: u8,
pub adr_control: u8,
pub track_num: u8,
pub point: u8,
pub sector_size: u32,
pub pregap_lba: i32,
pub start_lba: i32,
pub sector_count: u32,
pub pregap_sectors: u32,
pub file_offset: u64,
pub filename: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MdsDisc {
pub header: MdsHeader,
pub sessions: Vec<MdsSession>,
}
impl MdsDisc {
pub fn parse(data: &[u8]) -> Result<Self, DiscError> {
if data.len() < MDS_HEADER_MIN_SIZE {
return Err(DiscError::InvalidMds(format!(
"file too short: {} bytes (minimum {})",
data.len(),
MDS_HEADER_MIN_SIZE
)));
}
if !data.starts_with(MDS_MAGIC) {
return Err(DiscError::InvalidMds(
"invalid signature: missing 'MEDIA DESCRIPTOR' magic".to_string(),
));
}
let version_major = data[16];
let version_minor = data[17];
let medium_type = u16::from_le_bytes([data[18], data[19]]);
let num_sessions = u16::from_le_bytes([data[20], data[21]]);
if num_sessions == 0 {
return Err(DiscError::InvalidMds(
"MDS specifies 0 sessions".to_string(),
));
}
let sessions_offset = if data.len() >= 0x54 {
let off = u32::from_le_bytes([data[0x50], data[0x51], data[0x52], data[0x53]]);
if off > 0 && (off as usize) < data.len() {
off
} else if data.len() >= 0x4a {
u32::from_le_bytes([data[0x46], data[0x47], data[0x48], data[0x49]])
} else {
0x60
}
} else if data.len() >= 0x4a {
u32::from_le_bytes([data[0x46], data[0x47], data[0x48], data[0x49]])
} else {
0x60
};
let dpm_offset = if data.len() >= 0x2e {
u32::from_le_bytes([data[0x2a], data[0x2b], data[0x2c], data[0x2d]])
} else {
0
};
let header = MdsHeader {
version_major,
version_minor,
medium_type,
num_sessions,
sessions_offset,
dpm_offset,
};
let mut sessions = Vec::with_capacity(num_sessions as usize);
for s in 0..num_sessions as usize {
let sess_pos = (sessions_offset as usize) + s * MDS_SESSION_BLOCK_SIZE;
if sess_pos + MDS_SESSION_BLOCK_SIZE > data.len() {
return Err(DiscError::InvalidMds(format!(
"session block {} out of bounds at offset {:#x}",
s, sess_pos
)));
}
let s_data = &data[sess_pos..sess_pos + MDS_SESSION_BLOCK_SIZE];
let session_start_lba =
i32::from_le_bytes([s_data[0], s_data[1], s_data[2], s_data[3]]);
let session_end_lba = i32::from_le_bytes([s_data[4], s_data[5], s_data[6], s_data[7]]);
let session_number = u16::from_le_bytes([s_data[8], s_data[9]]);
let num_all_tracks = s_data[10];
let num_leadout_tracks = s_data[11];
let first_track_num = u16::from_le_bytes([s_data[12], s_data[13]]);
let last_track_num = u16::from_le_bytes([s_data[14], s_data[15]]);
let track_data_offset =
u32::from_le_bytes([s_data[20], s_data[21], s_data[22], s_data[23]]);
let mut tracks = Vec::with_capacity(num_all_tracks as usize);
for t in 0..num_all_tracks as usize {
let trk_pos = (track_data_offset as usize) + t * MDS_TRACK_BLOCK_SIZE;
if trk_pos + MDS_TRACK_BLOCK_SIZE > data.len() {
return Err(DiscError::InvalidMds(format!(
"track block {} out of bounds at offset {:#x}",
t, trk_pos
)));
}
let t_data = &data[trk_pos..trk_pos + MDS_TRACK_BLOCK_SIZE];
let mode = t_data[0];
let subchannel_mode = t_data[1];
let adr_control = t_data[2];
let track_num = t_data[3];
let point = t_data[4];
let extra_offset =
u32::from_le_bytes([t_data[12], t_data[13], t_data[14], t_data[15]]);
let sector_size = u16::from_le_bytes([t_data[16], t_data[17]]) as u32;
let pregap_lba = i32::from_le_bytes([t_data[8], t_data[9], t_data[10], t_data[11]]);
let start_lba =
i32::from_le_bytes([t_data[36], t_data[37], t_data[38], t_data[39]]);
let fn_block_offset =
u32::from_le_bytes([t_data[52], t_data[53], t_data[54], t_data[55]]);
let (pregap_sectors, sector_count) =
if extra_offset > 0 && (extra_offset as usize) + 8 <= data.len() {
let e_pos = extra_offset as usize;
let pg = u32::from_le_bytes([
data[e_pos],
data[e_pos + 1],
data[e_pos + 2],
data[e_pos + 3],
]);
let sc = u32::from_le_bytes([
data[e_pos + 4],
data[e_pos + 5],
data[e_pos + 6],
data[e_pos + 7],
]);
(pg, sc)
} else {
(0, 0)
};
let filename =
if fn_block_offset > 0 && (fn_block_offset as usize) + 8 <= data.len() {
let fn_pos = fn_block_offset as usize;
let name_offset = u32::from_le_bytes([
data[fn_pos],
data[fn_pos + 1],
data[fn_pos + 2],
data[fn_pos + 3],
]) as usize;
let is_wide = u32::from_le_bytes([
data[fn_pos + 4],
data[fn_pos + 5],
data[fn_pos + 6],
data[fn_pos + 7],
]);
if name_offset > 0 && name_offset < data.len() {
let raw_slice = &data[name_offset..];
if is_wide == 1 {
let mut u16_chars = Vec::new();
for chunk in raw_slice.as_chunks::<2>().0 {
let c = u16::from_le_bytes(*chunk);
if c == 0 {
break;
}
u16_chars.push(c);
}
String::from_utf16_lossy(&u16_chars)
} else {
let end = raw_slice
.iter()
.position(|&b| b == 0)
.unwrap_or(raw_slice.len());
String::from_utf8_lossy(&raw_slice[..end]).into_owned()
}
} else {
"*.mdf".to_string()
}
} else {
"*.mdf".to_string()
};
let effective_sec_size = if sector_size == 0 {
if subchannel_mode == 0x08 {
2448
} else {
2352
}
} else {
sector_size
};
tracks.push(MdsTrack {
mode,
subchannel_mode,
adr_control,
track_num,
point,
sector_size: effective_sec_size,
pregap_lba,
start_lba,
sector_count,
pregap_sectors,
file_offset: 0,
filename,
});
}
sessions.push(MdsSession {
session_start_lba,
session_end_lba,
session_number,
num_all_tracks,
num_leadout_tracks,
first_track_num,
last_track_num,
track_data_offset,
tracks,
});
}
Ok(Self { header, sessions })
}
pub fn parse_file<P: AsRef<Path>>(path: P) -> Result<CdToc, DiscError> {
let p = path.as_ref();
let bytes = fs::read(p)?;
let mds = Self::parse(&bytes)?;
mds.to_toc(p)
}
pub fn to_toc(&self, mds_path: &Path) -> Result<CdToc, DiscError> {
let mut user_tracks = Vec::new();
for session in &self.sessions {
for track in &session.tracks {
if track.point >= 1 && track.point <= 99 && track.track_num != 0xaa {
user_tracks.push(track.clone());
}
}
}
if user_tracks.is_empty() {
return Err(DiscError::InvalidMds(
"no valid user tracks found in MDS".to_string(),
));
}
user_tracks.sort_by_key(|t| t.point);
let parent_dir = mds_path.parent().unwrap_or_else(|| Path::new("."));
let mds_stem = mds_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("disc");
let default_mdf_name = format!("{}.mdf", mds_stem);
let mut cumulative_file_offset: u64 = 0;
let mut cd_tracks = Vec::with_capacity(user_tracks.len());
let total_tracks = user_tracks.len();
for (i, trk) in user_tracks.iter().enumerate() {
let track_num = trk.point as u32;
let track_type = if (trk.adr_control & 0x04) == 0 || trk.mode == 0 {
CdTrackType::Audio
} else if trk.sector_size == 2048 {
CdTrackType::Mode1
} else if trk.mode == 2 {
CdTrackType::Mode2Raw
} else {
CdTrackType::Mode1Raw
};
let subcode_type = if trk.subchannel_mode == 0x08
|| trk.sector_size == 2448
|| trk.sector_size == 2144
{
CdSubcodeType::RwRaw
} else {
CdSubcodeType::None
};
let frames = if trk.sector_count > 0 {
trk.sector_count
} else if i + 1 < total_tracks && user_tracks[i + 1].start_lba > trk.start_lba {
(user_tracks[i + 1].start_lba - trk.start_lba) as u32
} else if let Some(first_sess) = self.sessions.first() {
if first_sess.session_end_lba > trk.start_lba {
(first_sess.session_end_lba - trk.start_lba) as u32
} else {
0
}
} else {
0
};
let base_mdf = if trk.filename == "*.mdf" || trk.filename.is_empty() {
default_mdf_name.clone()
} else {
Path::new(&trk.filename)
.file_name()
.and_then(|f| f.to_str())
.unwrap_or(&default_mdf_name)
.to_string()
};
let resolved_filename = if parent_dir.join(&base_mdf).is_file() {
base_mdf
} else if parent_dir.join(format!("{}.mdf", mds_stem)).is_file() {
format!("{}.mdf", mds_stem)
} else if parent_dir.join(format!("{}.MDF", mds_stem)).is_file() {
format!("{}.MDF", mds_stem)
} else if parent_dir.join(base_mdf.to_lowercase()).is_file() {
base_mdf.to_lowercase()
} else if parent_dir.join(base_mdf.to_uppercase()).is_file() {
base_mdf.to_uppercase()
} else {
base_mdf
};
let pregap_frames = if trk.pregap_sectors > 0 {
trk.pregap_sectors
} else if trk.start_lba > trk.pregap_lba && trk.pregap_lba >= 0 {
(trk.start_lba - trk.pregap_lba) as u32
} else {
0
};
let mut cd_track = CdTrack::new(track_num, track_type, frames);
cd_track.subcode_type = subcode_type;
cd_track.pregap_frames = pregap_frames;
cd_track.file_name = Some(resolved_filename.clone());
cd_track.file_offset = cumulative_file_offset;
let track_byte_size = (frames as u64) * (trk.sector_size as u64);
cumulative_file_offset += track_byte_size;
cd_tracks.push(cd_track);
}
let mdf_path = cd_tracks
.first()
.and_then(|t| t.file_name.as_ref())
.map(|f| parent_dir.join(f))
.unwrap_or_else(|| parent_dir.join(&default_mdf_name));
if let Ok(metadata) = fs::metadata(&mdf_path) {
let mdf_bytes = metadata.len();
if let Some(last_track) = cd_tracks.last_mut() {
if last_track.frames == 0 {
let sec_size = if last_track.subcode_type == CdSubcodeType::RwRaw {
2448
} else {
last_track.track_type.sector_size() as u64
};
let remaining_bytes = mdf_bytes.saturating_sub(last_track.file_offset);
last_track.frames = (remaining_bytes / sec_size) as u32;
}
}
}
Ok(CdToc {
disc_type: DiscType::Cd,
tracks: cd_tracks,
})
}
pub fn format(toc: &CdToc) -> Vec<u8> {
let num_user_tracks = toc.tracks.len();
let num_leadout = 3usize; let total_tracks = num_leadout + num_user_tracks;
let header_size = 96usize; let session_block_size = MDS_SESSION_BLOCK_SIZE; let sessions_offset = header_size; let track_data_offset = sessions_offset + session_block_size;
let track_blocks_size = total_tracks * MDS_TRACK_BLOCK_SIZE; let extra_blocks_offset = track_data_offset + track_blocks_size;
let extra_block_size = 24usize;
let extra_blocks_total_size = num_user_tracks * extra_block_size;
let fn_blocks_offset = extra_blocks_offset + extra_blocks_total_size;
let fn_block_size = 16usize;
let fn_blocks_total_size = num_user_tracks * fn_block_size;
let fn_string_offset = fn_blocks_offset + fn_blocks_total_size;
let fn_utf16: Vec<u8> = "*.mdf\0"
.encode_utf16()
.flat_map(|u| u.to_le_bytes())
.collect();
let total_file_size = fn_string_offset + fn_utf16.len();
let mut buf = vec![0u8; total_file_size];
buf[0..16].copy_from_slice(MDS_MAGIC);
buf[16] = 0x01; buf[17] = 0x05; buf[18..20].copy_from_slice(&MDS_MEDIUM_CD.to_le_bytes());
buf[20..22].copy_from_slice(&1u16.to_le_bytes()); buf[22..24].copy_from_slice(&2u16.to_le_bytes()); buf[0x50..0x54].copy_from_slice(&(sessions_offset as u32).to_le_bytes());
let total_frames: u32 = toc.tracks.iter().map(|t| t.frames).sum();
let leadout_lba = total_frames as i32;
let s_pos = sessions_offset;
buf[s_pos..s_pos + 4].copy_from_slice(&(-150i32).to_le_bytes());
buf[s_pos + 4..s_pos + 8].copy_from_slice(&(leadout_lba - 1).to_le_bytes());
buf[s_pos + 8..s_pos + 10].copy_from_slice(&1u16.to_le_bytes()); buf[s_pos + 10] = total_tracks as u8;
buf[s_pos + 11] = num_leadout as u8;
buf[s_pos + 12..s_pos + 14].copy_from_slice(&1u16.to_le_bytes());
buf[s_pos + 14..s_pos + 16].copy_from_slice(&(num_user_tracks as u16).to_le_bytes());
buf[s_pos + 20..s_pos + 24].copy_from_slice(&(track_data_offset as u32).to_le_bytes());
let b0 = track_data_offset;
buf[b0] = 0x00;
buf[b0 + 1] = 0x00;
buf[b0 + 2] = 0x14; buf[b0 + 3] = 0x00;
buf[b0 + 4] = 0xa0;
buf[b0 + 8..b0 + 10].copy_from_slice(&1u16.to_le_bytes());
let b1 = track_data_offset + MDS_TRACK_BLOCK_SIZE;
buf[b1] = 0x00;
buf[b1 + 1] = 0x00;
buf[b1 + 2] = 0x14;
buf[b1 + 3] = 0x00;
buf[b1 + 4] = 0xa1;
buf[b1 + 8..b1 + 10].copy_from_slice(&(num_user_tracks as u16).to_le_bytes());
let b2 = track_data_offset + 2 * MDS_TRACK_BLOCK_SIZE;
buf[b2] = 0x00;
buf[b2 + 1] = 0x00;
buf[b2 + 2] = 0x14;
buf[b2 + 3] = 0x00;
buf[b2 + 4] = 0xa2;
let leadout_msf = Msf::from_lba((leadout_lba + 150) as u32);
buf[b2 + 9] = dec_to_bcd(leadout_msf.m);
buf[b2 + 10] = dec_to_bcd(leadout_msf.s);
buf[b2 + 11] = dec_to_bcd(leadout_msf.f);
let mut current_lba: i32 = 0;
for (i, trk) in toc.tracks.iter().enumerate() {
let blk_pos = track_data_offset + (num_leadout + i) * MDS_TRACK_BLOCK_SIZE;
let extra_pos = extra_blocks_offset + i * extra_block_size;
let fn_blk_pos = fn_blocks_offset + i * fn_block_size;
let is_audio = trk.track_type.is_audio();
let is_raw_sub = trk.subcode_type == CdSubcodeType::RwRaw;
let mode: u8 = if is_audio { 0x00 } else { 0xaa };
let subchannel_mode: u8 = if is_raw_sub { 0x08 } else { 0x00 };
let adr_control: u8 = if is_audio { 0x10 } else { 0x14 };
let sec_size: u16 = if is_raw_sub {
2448
} else {
trk.track_type.sector_size() as u16
};
buf[blk_pos] = mode;
buf[blk_pos + 1] = subchannel_mode;
buf[blk_pos + 2] = adr_control;
buf[blk_pos + 3] = 0x00;
buf[blk_pos + 4] = trk.track_num as u8;
buf[blk_pos + 8..blk_pos + 12].copy_from_slice(&0x00020000u32.to_le_bytes());
buf[blk_pos + 12..blk_pos + 16].copy_from_slice(&(extra_pos as u32).to_le_bytes());
buf[blk_pos + 16..blk_pos + 18].copy_from_slice(&sec_size.to_le_bytes());
buf[blk_pos + 18..blk_pos + 20].copy_from_slice(&2u16.to_le_bytes());
buf[blk_pos + 36..blk_pos + 40].copy_from_slice(¤t_lba.to_le_bytes());
buf[blk_pos + 48..blk_pos + 52].copy_from_slice(&1u32.to_le_bytes());
buf[blk_pos + 52..blk_pos + 56].copy_from_slice(&(fn_blk_pos as u32).to_le_bytes());
let pregap = if trk.pregap_frames > 0 {
trk.pregap_frames
} else {
150
};
buf[extra_pos..extra_pos + 4].copy_from_slice(&pregap.to_le_bytes());
buf[extra_pos + 4..extra_pos + 8].copy_from_slice(&trk.frames.to_le_bytes());
buf[extra_pos + 8..extra_pos + 12]
.copy_from_slice(&(fn_string_offset as u32).to_le_bytes());
buf[extra_pos + 12..extra_pos + 16].copy_from_slice(&1u32.to_le_bytes());
buf[fn_blk_pos..fn_blk_pos + 4]
.copy_from_slice(&(fn_string_offset as u32).to_le_bytes());
buf[fn_blk_pos + 4..fn_blk_pos + 8].copy_from_slice(&1u32.to_le_bytes());
current_lba += trk.frames as i32;
}
buf[fn_string_offset..fn_string_offset + fn_utf16.len()].copy_from_slice(&fn_utf16);
buf
}
}
fn dec_to_bcd(d: u8) -> u8 {
((d / 10) << 4) | (d % 10)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_parse_mds_header_and_signature() {
let mut data = vec![0u8; 120];
data[0..16].copy_from_slice(MDS_MAGIC);
data[16] = 1;
data[17] = 5;
data[18..20].copy_from_slice(&MDS_MEDIUM_CD.to_le_bytes());
data[20..22].copy_from_slice(&1u16.to_le_bytes());
data[0x50..0x54].copy_from_slice(&0x60u32.to_le_bytes());
let s_pos = 0x60;
data[s_pos..s_pos + 4].copy_from_slice(&(-150i32).to_le_bytes());
data[s_pos + 4..s_pos + 8].copy_from_slice(&1000i32.to_le_bytes());
data[s_pos + 8..s_pos + 10].copy_from_slice(&1u16.to_le_bytes());
data[s_pos + 10] = 0; data[s_pos + 20..s_pos + 24].copy_from_slice(&0x78u32.to_le_bytes());
let disc = MdsDisc::parse(&data).expect("valid parse");
assert_eq!(disc.header.version_major, 1);
assert_eq!(disc.header.version_minor, 5);
assert_eq!(disc.header.medium_type, MDS_MEDIUM_CD);
assert_eq!(disc.header.num_sessions, 1);
}
#[test]
fn test_parse_mds_invalid_magic() {
let data = vec![0u8; 100];
let err = MdsDisc::parse(&data).unwrap_err();
assert!(matches!(err, DiscError::InvalidMds(_)));
}
#[test]
fn test_format_and_parse_roundtrip() {
let mut toc = CdToc::new(DiscType::Cd);
let mut t1 = CdTrack::new(1, CdTrackType::Mode1Raw, 5000);
t1.subcode_type = CdSubcodeType::RwRaw;
toc.tracks.push(t1);
let mut t2 = CdTrack::new(2, CdTrackType::Audio, 3000);
t2.subcode_type = CdSubcodeType::RwRaw;
toc.tracks.push(t2);
let formatted = MdsDisc::format(&toc);
let parsed = MdsDisc::parse(&formatted).expect("parse formatted MDS");
assert_eq!(parsed.header.num_sessions, 1);
assert_eq!(parsed.sessions[0].num_all_tracks, 5); assert_eq!(parsed.sessions[0].first_track_num, 1);
assert_eq!(parsed.sessions[0].last_track_num, 2);
let conv_toc = parsed
.to_toc(Path::new("dummy.mds"))
.expect("convert to CdToc");
assert_eq!(conv_toc.tracks.len(), 2);
assert_eq!(conv_toc.tracks[0].track_num, 1);
assert_eq!(conv_toc.tracks[0].frames, 5000);
assert_eq!(conv_toc.tracks[0].subcode_type, CdSubcodeType::RwRaw);
assert_eq!(conv_toc.tracks[1].track_num, 2);
assert_eq!(conv_toc.tracks[1].frames, 3000);
assert_eq!(conv_toc.tracks[1].track_type, CdTrackType::Audio);
}
#[test]
fn test_parse_mds_too_short() {
let data = vec![0u8; 40];
let err = MdsDisc::parse(&data).unwrap_err();
assert!(matches!(err, DiscError::InvalidMds(_)));
}
#[test]
fn test_parse_mds_zero_sessions() {
let mut data = vec![0u8; 96];
data[0..16].copy_from_slice(MDS_MAGIC);
data[20..22].copy_from_slice(&0u16.to_le_bytes());
let err = MdsDisc::parse(&data).unwrap_err();
assert!(matches!(err, DiscError::InvalidMds(_)));
}
#[test]
fn test_parse_mds_session_out_of_bounds() {
let mut data = vec![0u8; 96];
data[0..16].copy_from_slice(MDS_MAGIC);
data[20..22].copy_from_slice(&1u16.to_le_bytes());
data[0x50..0x54].copy_from_slice(&500u32.to_le_bytes()); let err = MdsDisc::parse(&data).unwrap_err();
assert!(matches!(err, DiscError::InvalidMds(_)));
}
#[test]
fn test_parse_mds_leadout_and_leadin_filtering() {
let mut toc = CdToc::new(DiscType::Cd);
let mut t1 = CdTrack::new(1, CdTrackType::Mode1Raw, 1000);
t1.subcode_type = CdSubcodeType::None;
toc.tracks.push(t1);
let formatted = MdsDisc::format(&toc);
let disc = MdsDisc::parse(&formatted).expect("parse");
assert_eq!(disc.sessions[0].tracks.len(), 4);
assert_eq!(disc.sessions[0].tracks[0].point, 0xa0);
assert_eq!(disc.sessions[0].tracks[1].point, 0xa1);
assert_eq!(disc.sessions[0].tracks[2].point, 0xa2);
assert_eq!(disc.sessions[0].tracks[3].point, 1);
let converted = disc.to_toc(Path::new("test.mds")).expect("to_toc");
assert_eq!(converted.tracks.len(), 1);
assert_eq!(converted.tracks[0].track_num, 1);
}
#[test]
fn test_parse_mds_sector_size_variants() {
let mut toc = CdToc::new(DiscType::Cd);
let mut t1 = CdTrack::new(1, CdTrackType::Mode1, 2000);
t1.subcode_type = CdSubcodeType::None;
toc.tracks.push(t1);
let mut t2 = CdTrack::new(2, CdTrackType::Audio, 3000);
t2.subcode_type = CdSubcodeType::None;
toc.tracks.push(t2);
let mut t3 = CdTrack::new(3, CdTrackType::Mode1Raw, 4000);
t3.subcode_type = CdSubcodeType::RwRaw;
toc.tracks.push(t3);
let formatted = MdsDisc::format(&toc);
let disc = MdsDisc::parse(&formatted).expect("parse");
let converted = disc.to_toc(Path::new("test.mds")).expect("to_toc");
assert_eq!(converted.tracks.len(), 3);
assert_eq!(converted.tracks[0].track_type, CdTrackType::Mode1);
assert_eq!(converted.tracks[0].subcode_type, CdSubcodeType::None);
assert_eq!(converted.tracks[1].track_type, CdTrackType::Audio);
assert_eq!(converted.tracks[1].subcode_type, CdSubcodeType::None);
assert_eq!(converted.tracks[2].track_type, CdTrackType::Mode1Raw);
assert_eq!(converted.tracks[2].subcode_type, CdSubcodeType::RwRaw);
}
}