From b952e6a3256a4710221430d2ca9ba59210a23361 Mon Sep 17 00:00:00 2001 From: dfgordon Date: Sun, 15 Oct 2023 14:15:04 -0400 Subject: [PATCH] Round out matrix of image types and FAT disks --- README.md | 7 ++--- src/bios/bpb.rs | 12 +++++---- src/fs/fat/mod.rs | 1 + src/img/imd.rs | 16 +++--------- src/img/mod.rs | 3 ++- src/img/names.rs | 12 ++++----- src/img/td0.rs | 57 +++++++++++++++++++++++++++++++---------- tests/cli_test.rs | 40 +++++++++++++++++++++++++++++ tests/cli_test_mkdsk.rs | 15 +++++++++++ 9 files changed, 122 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 35980ab..28bf479 100644 --- a/README.md +++ b/README.md @@ -6,11 +6,12 @@ Command line interface and library for manipulating retro disk images, file syst * Languages - Applesoft, Integer BASIC, Merlin Assembly - full syntax comprehension - tokenization, detokenization -* File Systems - DOS 3.x, ProDOS, CP/M, Pascal +* File Systems - Apple DOS 3.x, ProDOS, CP/M, Pascal, FAT (such as MS-DOS) - full read and write access - high or low level manipulations - - interface for handling sparse (random access) files -* Disk Images - 2MG, D13, DO, DSK, IMD, NIB, PO, TD0, WOZ + - interface for handling sparse and random access files +* Disk Images - 2MG, D13, DO, DSK, IMD, IMG, NIB, PO, TD0, WOZ + - create, read, and write with all types ## Documentation diff --git a/src/bios/bpb.rs b/src/bios/bpb.rs index a0a0f1d..716e7f7 100644 --- a/src/bios/bpb.rs +++ b/src/bios/bpb.rs @@ -14,8 +14,8 @@ use crate::{STDRESULT,DYNERR}; use a2kit_macro::DiskStruct; use a2kit_macro_derive::DiskStruct; -const JMP_BOOT: [u8;3] = [0xeb,0x00,0x90]; -const OEM_NAME: [u8;8] = *b"MSWIN4.1"; +const JMP_BOOT: [u8;3] = [0xeb,0x58,0x90]; +const OEM_NAME: [u8;8] = *b"A2KITX.X"; const BOOT_SIGNATURE: [u8;2] = [0x55,0xaa]; // goes in boot[510..512] const RCH: &str = "unreachable was reached"; @@ -295,9 +295,11 @@ impl BootSector { let mut remainder: Vec = vec![0;sec_size - used]; remainder[510-used] = BOOT_SIGNATURE[0]; remainder[511-used] = BOOT_SIGNATURE[1]; + let mut oem = OEM_NAME; + oem[5..8].copy_from_slice(&env!("CARGO_PKG_VERSION").as_bytes()[0..3]); Self { jmp: JMP_BOOT, - oem: OEM_NAME, + oem, foundation: bpb, extension32: BPBExtension32::new(), tail, @@ -564,7 +566,7 @@ const D35_1440: BPBFoundation = BPBFoundation { tot_sec_16: u16::to_le_bytes(2880), media: 0xf0, fat_size_16: [9,0], - sec_per_trk: [12,0], + sec_per_trk: [18,0], num_heads: [2,0], hidd_sec: [0,0,0,0], tot_sec_32: [0,0,0,0] @@ -579,7 +581,7 @@ const D35_2880: BPBFoundation = BPBFoundation { tot_sec_16: u16::to_le_bytes(5760), media: 0xf0, fat_size_16: [9,0], - sec_per_trk: [24,0], + sec_per_trk: [36,0], num_heads: [2,0], hidd_sec: [0,0,0,0], tot_sec_32: [0,0,0,0] diff --git a/src/fs/fat/mod.rs b/src/fs/fat/mod.rs index ff80f91..c5dd988 100644 --- a/src/fs/fat/mod.rs +++ b/src/fs/fat/mod.rs @@ -261,6 +261,7 @@ impl Disk { // Start with 0 in reserved/FAT/root, f6 in data region // n.b. blocks can only access the data region. trace!("fill sectors with 0x00 and 0xf6"); + trace!("disk has {} tracks {} sectors",self.img.track_count(),self.boot_sector.secs_per_track()); let zeroes: Vec = vec![0;sec_size]; let f6: Vec = vec![0xf6;sec_size]; for lsec in 0..self.boot_sector.tot_sec() as usize { diff --git a/src/img/imd.rs b/src/img/imd.rs index 8b2ae5f..aa1901e 100644 --- a/src/img/imd.rs +++ b/src/img/imd.rs @@ -111,29 +111,18 @@ impl Track { (super::FluxCode::MFM,super::DataRate::R500Kbps) => Mode::Mfm500Kbps, _ => panic!("unhandled track mode") }; + let default_map: Vec = (1..layout.sectors[0] as u8 + 1).collect(); let sector_map: Vec = match *layout { - super::names::CPM_1 => (1..27).collect(), - super::names::AMSTRAD_SS => (1..10).collect(), super::names::KAYPROII => (0..10).collect(), super::names::KAYPRO4 => match track_num%2 { 0 => (0..10).collect(), _ => (10..20).collect(), }, - super::names::OSBORNE1_SD => (1..11).collect(), - super::names::OSBORNE1_DD => [1,2,3,4,5].to_vec(), super::names::TRS80_M2_CPM => match track_num { 0 => (1..27).collect(), _ => (1..17).collect(), }, - super::names::NABU_CPM => (1..27).collect(), - super::names::IBM_SSDD_8 => (1..9).collect(), - super::names::IBM_DSDD_8 => (1..9).collect(), - //super::names::IBM_SSDD_9 => (1..10).collect(), // same pattern as amstrad - super::names::IBM_DSDD_9 => (1..10).collect(), - super::names::IBM_SSQD => (1..9).collect(), - super::names::IBM_DSQD => (1..9).collect(), - super::names::IBM_DSHD => (1..16).collect(), - _ => panic!("unhandled track layout") + _ => default_map }; let cylinder_map: Vec = Vec::new(); let head_map: Vec = match *layout { @@ -351,6 +340,7 @@ impl Imd { debug!("header {}",header); let (heads,tracks) = match kind { img::DiskKind::D3(layout) | + img::DiskKind::D35(layout) | img::DiskKind::D525(layout) | img::DiskKind::D8(layout) => { let mut ans: Vec = Vec::new(); diff --git a/src/img/mod.rs b/src/img/mod.rs index a78f391..f83af5f 100644 --- a/src/img/mod.rs +++ b/src/img/mod.rs @@ -114,7 +114,8 @@ pub enum NibbleCode { pub enum DataRate { R250Kbps, R300Kbps, - R500Kbps + R500Kbps, + R1000Kbps } #[derive(PartialEq,Eq,Clone,Copy)] diff --git a/src/img/names.rs b/src/img/names.rs index 9c596d6..0a5c2fd 100644 --- a/src/img/names.rs +++ b/src/img/names.rs @@ -135,7 +135,7 @@ pub const KAYPRO4: TrackLayout = TrackLayout { data_rate: [DataRate::R250Kbps;5] }; -// TODO: verify the data rates on these IBM disks +// data rates are from ImageDisk by Dave Dunfield, except 2.88 where we are guessing pub const IBM_SSDD_8: TrackLayout = TrackLayout { cylinders: uni!(40), @@ -204,7 +204,7 @@ pub const IBM_DSHD: TrackLayout = TrackLayout { sectors: uni!(15), flux_code: [FluxCode::MFM;5], nib_code: [NibbleCode::None;5], - data_rate: [DataRate::R250Kbps;5] + data_rate: [DataRate::R500Kbps;5] }; pub const IBM_720: TrackLayout = TrackLayout { @@ -224,7 +224,7 @@ pub const IBM_1440: TrackLayout = TrackLayout { sectors: uni!(18), flux_code: [FluxCode::MFM;5], nib_code: [NibbleCode::None;5], - data_rate: [DataRate::R250Kbps;5] + data_rate: [DataRate::R500Kbps;5] }; pub const IBM_1680: TrackLayout = TrackLayout { @@ -234,7 +234,7 @@ pub const IBM_1680: TrackLayout = TrackLayout { sectors: uni!(21), flux_code: [FluxCode::MFM;5], nib_code: [NibbleCode::None;5], - data_rate: [DataRate::R250Kbps;5] + data_rate: [DataRate::R500Kbps;5] }; pub const IBM_1720: TrackLayout = TrackLayout { @@ -244,7 +244,7 @@ pub const IBM_1720: TrackLayout = TrackLayout { sectors: uni!(21), flux_code: [FluxCode::MFM;5], nib_code: [NibbleCode::None;5], - data_rate: [DataRate::R250Kbps;5] + data_rate: [DataRate::R500Kbps;5] }; pub const IBM_2880: TrackLayout = TrackLayout { @@ -254,7 +254,7 @@ pub const IBM_2880: TrackLayout = TrackLayout { sectors: uni!(36), flux_code: [FluxCode::MFM;5], nib_code: [NibbleCode::None;5], - data_rate: [DataRate::R250Kbps;5] + data_rate: [DataRate::R1000Kbps;5] }; // This kind might contain DOS 3.0, 3.1, or 3.2. diff --git a/src/img/td0.rs b/src/img/td0.rs index cf5eb08..78199dd 100644 --- a/src/img/td0.rs +++ b/src/img/td0.rs @@ -357,22 +357,18 @@ impl Track { fn create(track_num: usize, layout: &super::TrackLayout) -> Self { let zone = layout.zone(track_num); let head = (track_num % layout.sides[zone]) as u8; + let default_map: Vec = (1..layout.sectors[0] as u8 + 1).collect(); let sector_map: Vec = match *layout { - super::names::CPM_1 => (1..27).collect(), - super::names::AMSTRAD_SS => (1..10).collect(), super::names::KAYPROII => (0..10).collect(), super::names::KAYPRO4 => match track_num%2 { 0 => (0..10).collect(), _ => (10..20).collect(), }, - super::names::OSBORNE1_SD => (1..11).collect(), - super::names::OSBORNE1_DD => [1,2,3,4,5].to_vec(), super::names::TRS80_M2_CPM => match track_num { 0 => (1..27).collect(), _ => (1..17).collect(), }, - super::names::NABU_CPM => (1..27).collect(), - _ => panic!("unhandled track layout") + _ => default_map }; let head_map: Vec = match *layout { super::names::KAYPRO4 => match track_num%2 { @@ -489,6 +485,7 @@ impl Td0 { let comment_string = "created by a2kit v".to_string() + env!("CARGO_PKG_VERSION"); let layout = match kind { img::DiskKind::D3(layout) => layout, + img::DiskKind::D35(layout) => layout, img::DiskKind::D525(layout) => layout, img::DiskKind::D8(layout) => layout, _ => panic!("cannot create this kind of disk in TD0 format") @@ -505,7 +502,7 @@ impl Td0 { (super::DataRate::R300Kbps,super::FluxCode::MFM) => 0x01, (super::DataRate::R500Kbps,super::FluxCode::MFM) => 0x02, _ => { - panic!("unsupported flux encoding"); + panic!("unsupported data rate and flux encoding"); } }; let drive_type = match kind { @@ -623,10 +620,10 @@ impl img::DiskImage for Td0 { trace!("reading {}",addr); match addr { Block::CPM((_block,_bsh,off)) => { - let sectors = self.tracks[off as usize].sectors.len(); + let secs_per_track = self.tracks[off as usize].sectors.len(); let sector_shift = self.tracks[off as usize].sectors[0].header.sector_shift; let mut ans: Vec = Vec::new(); - let deblocked_ts_list = addr.get_lsecs((sectors << sector_shift) as usize); + let deblocked_ts_list = addr.get_lsecs((secs_per_track << sector_shift) as usize); let chs_list = skew::cpm_blocking(deblocked_ts_list, sector_shift,self.heads)?; for [cyl,head,lsec] in chs_list { self.check_user_area_up_to_cyl(cyl, off)?; @@ -639,7 +636,23 @@ impl img::DiskImage for Td0 { } } Ok(ans) - } + }, + Block::FAT((_sec1,_secs)) => { + let secs_per_track = self.tracks[0].sectors.len(); + let mut ans: Vec = Vec::new(); + let deblocked_ts_list = addr.get_lsecs(secs_per_track); + let chs_list = skew::fat_blocking(deblocked_ts_list,self.heads)?; + for [cyl,head,lsec] in chs_list { + self.check_user_area_up_to_cyl(cyl, 0)?; + match self.read_sector(cyl,head,lsec) { + Ok(mut slice) => { + ans.append(&mut slice); + }, + Err(e) => return Err(e) + } + } + Ok(ans) + }, _ => Err(Box::new(img::Error::ImageTypeMismatch)) } } @@ -647,9 +660,9 @@ impl img::DiskImage for Td0 { trace!("writing {}",addr); match addr { Block::CPM((_block,_bsh,off)) => { - let sectors = self.tracks[off as usize].sectors.len(); + let secs_per_track = self.tracks[off as usize].sectors.len(); let sector_shift = self.tracks[off as usize].sectors[0].header.sector_shift; - let deblocked_ts_list = addr.get_lsecs((sectors << sector_shift) as usize); + let deblocked_ts_list = addr.get_lsecs((secs_per_track << sector_shift) as usize); let chs_list = skew::cpm_blocking(deblocked_ts_list, sector_shift,self.heads)?; let mut src_offset = 0; let psec_size = SECTOR_SIZE_BASE << sector_shift; @@ -663,7 +676,25 @@ impl img::DiskImage for Td0 { } } Ok(()) - } + }, + Block::FAT((_sec1,_secs)) => { + // TODO: do we need to handle variable sectors per track + let secs_per_track = self.tracks[0].sectors.len(); + let sector_shift = self.tracks[0].sectors[0].header.sector_shift; + let sec_size = 128 << sector_shift; + let deblocked_ts_list = addr.get_lsecs(secs_per_track); + let chs_list = skew::fat_blocking(deblocked_ts_list,self.heads)?; + let mut src_offset = 0; + let padded = super::quantize_block(dat, chs_list.len()*sec_size); + for [cyl,head,lsec] in chs_list { + self.check_user_area_up_to_cyl(cyl, 0)?; + match self.write_sector(cyl,head,lsec,&padded[src_offset..src_offset+sec_size].to_vec()) { + Ok(_) => src_offset += sec_size, + Err(e) => return Err(e) + } + } + Ok(()) + }, _ => Err(Box::new(img::Error::ImageTypeMismatch)) } } diff --git a/tests/cli_test.rs b/tests/cli_test.rs index b56121d..38af927 100644 --- a/tests/cli_test.rs +++ b/tests/cli_test.rs @@ -50,6 +50,28 @@ A: POLARIS BAK : POLARIS TXT Ok(()) } +#[test] +fn catalog_cpm_wildcard_full() -> STDRESULT { + let mut cmd = Command::cargo_bin("a2kit")?; + let expected = +r#"Directory for Drive A: User 0 + + Name Bytes Recs Attributes Name Bytes Recs Attributes\s* +------------ ------ ------ ------------ ------------ ------ ------ ------------ + +POLARIS TXT 1k 4 Dir RW\s* + +Total Bytes = 1k Total Records = 4 Files Found = 1 +Total 1k Blocks = 1 Occupied/Tot Entries For Drive A: 2/ 48"#; + cmd.arg("catalog") + .arg("-d").arg(Path::new("tests").join("cpm-smallfiles.dsk")) + .arg("-f").arg("*.txt[full]") + .assert() + .success() + .stdout(predicate::str::is_match(expected).expect("regex error")); + Ok(()) +} + #[test] fn catalog_dos32() -> STDRESULT { let mut cmd = Command::cargo_bin("a2kit")?; @@ -146,6 +168,24 @@ DIR3\s+.* Ok(()) } +#[test] +fn catalog_msdos_wildcard() -> STDRESULT { + let mut cmd = Command::cargo_bin("a2kit")?; + let expected = +r#" Volume in drive A is NEW DISK 1 + Directory of A:\\DIR1\\SUB\*\.\* + +SUBDIR1\s+.* +\s+1 File\(s\)\s+135168 bytes free"#; + cmd.arg("catalog") + .arg("-d").arg(Path::new("tests").join("msdos-ren-del.img")) + .arg("-f").arg("/dir1/sub*.*") + .assert() + .success() + .stdout(predicate::str::is_match(expected).expect("regex err")); + Ok(()) +} + // N.b. extensive tokenization tests are in the language modules #[test] fn tokenize_stdin() -> STDRESULT { diff --git a/tests/cli_test_mkdsk.rs b/tests/cli_test_mkdsk.rs index c221bc3..a690f83 100644 --- a/tests/cli_test_mkdsk.rs +++ b/tests/cli_test_mkdsk.rs @@ -139,3 +139,18 @@ fn mk_fat_imd() -> STDRESULT { .success(); Ok(()) } + +#[test] +fn mk_fat_td0() -> STDRESULT { + let mut cmd = Command::cargo_bin("a2kit")?; + let dir = tempfile::tempdir()?; + let dimg_path = dir.path().join("fat.td0"); + cmd.arg("mkdsk") + .arg("-t").arg("td0").arg("-o").arg("fat") + .arg("-k").arg("3.5in-ibm-720") + .arg("-d").arg(dimg_path) + .arg("-v").arg("volume 1") + .assert() + .success(); + Ok(()) +}