use crate::sc64::Error; use std::{ collections::HashMap, fs::File, io::{Read, Seek, SeekFrom, Write}, }; const BLOCKS_PER_TRACK: usize = 2; const SECTORS_PER_BLOCK: usize = 85; const SYSTEM_SECTOR_LENGTH: usize = 232; const BAD_TRACKS_PER_ZONE: usize = 12; #[derive(Clone, Copy, PartialEq)] pub enum Format { Retail, Development, } struct SystemAreaInfo<'a> { format: Format, sector_length: usize, sys_lba: &'a [usize], bad_lba: &'a [usize], } const SYSTEM_AREA: [SystemAreaInfo; 2] = [ SystemAreaInfo { format: Format::Retail, sector_length: 232, sys_lba: &[9, 8, 1, 0], bad_lba: &[2, 3, 10, 11, 12, 16, 17, 18, 19, 20, 21, 22, 23], }, SystemAreaInfo { format: Format::Development, sector_length: 192, sys_lba: &[11, 10, 3, 2], bad_lba: &[0, 1, 8, 9, 16, 17, 18, 19, 20, 21, 22, 23], }, ]; const ID_LBAS: [usize; 2] = [15, 14]; struct DiskZone { head: usize, sector_length: usize, tracks: usize, track_offset: usize, } macro_rules! zone { ($h:expr, $l:expr, $t:expr, $o:expr) => { DiskZone { head: $h, sector_length: $l, tracks: $t, track_offset: $o, } }; } const ZONE_MAPPING: [DiskZone; 16] = [ zone!(0, 232, 158, 0), zone!(0, 216, 158, 158), zone!(0, 208, 149, 316), zone!(0, 192, 149, 465), zone!(0, 176, 149, 614), zone!(0, 160, 149, 763), zone!(0, 144, 149, 912), zone!(0, 128, 114, 1061), zone!(1, 216, 158, 0), zone!(1, 208, 158, 158), zone!(1, 192, 149, 316), zone!(1, 176, 149, 465), zone!(1, 160, 149, 614), zone!(1, 144, 149, 763), zone!(1, 128, 149, 912), zone!(1, 112, 114, 1061), ]; const VZONE_TO_PZONE: [[usize; 16]; 7] = [ [0, 1, 2, 9, 8, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10], [0, 1, 2, 3, 10, 9, 8, 4, 5, 6, 7, 15, 14, 13, 12, 11], [0, 1, 2, 3, 4, 11, 10, 9, 8, 5, 6, 7, 15, 14, 13, 12], [0, 1, 2, 3, 4, 5, 12, 11, 10, 9, 8, 6, 7, 15, 14, 13], [0, 1, 2, 3, 4, 5, 6, 13, 12, 11, 10, 9, 8, 7, 15, 14], [0, 1, 2, 3, 4, 5, 6, 7, 14, 13, 12, 11, 10, 9, 8, 15], [0, 1, 2, 3, 4, 5, 6, 7, 15, 14, 13, 12, 11, 10, 9, 8], ]; const ROM_ZONES: [usize; 7] = [5, 7, 9, 11, 13, 15, 16]; struct Mapping { lba: usize, offset: usize, length: usize, writable: bool, } pub struct Disk { file: File, format: Format, mapping: HashMap, } impl Disk { pub fn get_format(&self) -> &Format { &self.format } pub fn get_lba(&self, track: u32, head: u32, block: u32) -> Option { if head == 0 && track < 12 { return Some((track << 1 | block ^ (track % 2)) as usize); } let location = track << 2 | head << 1 | block; self.mapping .get(&(location as usize)) .map(|block| block.lba) } pub fn read_block( &mut self, track: u32, head: u32, block: u32, ) -> Result>, Error> { let location = track << 2 | head << 1 | block; if let Some(block) = self.mapping.get(&(location as usize)) { let mut data = vec![0u8; block.length]; self.file.seek(SeekFrom::Start(block.offset as u64))?; self.file.read_exact(&mut data)?; return Ok(Some(data)); } Ok(None) } pub fn write_block( &mut self, track: u32, head: u32, block: u32, data: &[u8], ) -> Result, Error> { let location = track << 2 | head << 1 | block; if let Some(block) = self.mapping.get(&(location as usize)) { if block.length == data.len() && block.writable { self.file.seek(SeekFrom::Start(block.offset as u64))?; self.file.write_all(data)?; return Ok(Some(())); } } Ok(None) } } pub fn open(path: &str) -> Result { let mut file = File::options().read(true).write(true).open(path)?; let (format, mapping) = load_ndd(&mut file)?; Ok(Disk { file, format, mapping, }) } pub fn open_multiple(paths: &[String]) -> Result, Error> { let mut disks: Vec = Vec::new(); for path in paths { let disk = open(path)?; disks.push(disk); } if !disks.windows(2).all(|d| d[0].format == d[1].format) { return Err(Error::new("Disk format mismatch")); } Ok(disks) } fn load_ndd(file: &mut File) -> Result<(Format, HashMap), Error> { let mut disk_format: Option = None; let mut disk_type: usize = 0; let mut sys_data = vec![0u8; SYSTEM_SECTOR_LENGTH]; let mut bad_lbas: Vec = Vec::new(); for info in SYSTEM_AREA { bad_lbas.clear(); for &lba in info.sys_lba { let data = load_sys_lba(file, lba)?; if verify_sys_lba(&data, info.sector_length) { if (data[4] != 0x10) || ((data[5] & 0xF0) != 0x10) { bad_lbas.push(lba); } else { disk_format = Some(info.format); disk_type = (data[5] & 0x0F) as usize; sys_data = data[0..SYSTEM_SECTOR_LENGTH].to_vec(); } } else { bad_lbas.push(lba); } } if disk_format.is_some() { bad_lbas.append(&mut info.bad_lba.to_vec()); break; } } if disk_format.is_none() { return Err(Error::new("Provided 64DD disk file is not valid")); } if disk_type >= VZONE_TO_PZONE.len() { return Err(Error::new("Unknown disk type")); } let mut id_lba_valid = false; for lba in ID_LBAS { let data = load_sys_lba(file, lba)?; let valid = verify_sys_lba(&data, SYSTEM_SECTOR_LENGTH); if !valid { bad_lbas.push(lba); } id_lba_valid |= valid; } if !id_lba_valid { return Err(Error::new("No valid ID LBA found")); } let mut zone_bad_tracks: Vec> = Vec::new(); for (zone, info) in ZONE_MAPPING.iter().enumerate() { let mut bad_tracks: Vec = Vec::new(); let start = if zone == 0 { 0 } else { sys_data[0x07 + zone] }; let stop = sys_data[0x07 + zone + 1]; for offset in start..stop { bad_tracks.push(sys_data[0x20 + offset as usize] as usize); } for track in 0..(BAD_TRACKS_PER_ZONE - bad_tracks.len()) { bad_tracks.push(info.tracks - track - 1); } zone_bad_tracks.push(bad_tracks); } let mut mapping = HashMap::new(); let mut lba: usize = 0; let mut offset: usize = 0; let mut starting_block: usize = 0; for (vzone, &pzone) in VZONE_TO_PZONE[disk_type].iter().enumerate() { let DiskZone { head, sector_length, tracks, track_offset, } = ZONE_MAPPING[pzone]; let zone_tracks: Box> = if head == 0 { Box::new(0..tracks) } else { Box::new((0..tracks).rev()) }; for zone_track in zone_tracks { if !zone_bad_tracks[pzone].contains(&zone_track) { for block in 0..BLOCKS_PER_TRACK { let track = track_offset + zone_track; let location = (track << 2) | (head << 1) | (starting_block ^ block); let length = sector_length * SECTORS_PER_BLOCK; if !bad_lbas.contains(&lba) { let writable = vzone >= ROM_ZONES[disk_type]; mapping.insert( location, Mapping { lba, offset, length, writable, }, ); } lba += 1; offset += length; } starting_block ^= 1; } } } Ok((disk_format.unwrap(), mapping)) } fn load_sys_lba(file: &mut File, lba: usize) -> Result, Error> { let length = SYSTEM_SECTOR_LENGTH * SECTORS_PER_BLOCK; file.seek(SeekFrom::Start((lba * length) as u64))?; let mut data = vec![0u8; length]; file.read_exact(&mut data)?; Ok(data) } fn verify_sys_lba(data: &[u8], sector_length: usize) -> bool { let sys_data = &data[0..sector_length]; for sector in 1..SECTORS_PER_BLOCK { let offset = sector * sector_length; let verify_data = &data[offset..(offset + sector_length)]; if sys_data != verify_data { return false; } } true }