Skip to content

Commit

Permalink
Round out matrix of image types and FAT disks
Browse files Browse the repository at this point in the history
  • Loading branch information
dfgordon committed Oct 15, 2023
1 parent ca74715 commit b952e6a
Show file tree
Hide file tree
Showing 9 changed files with 122 additions and 41 deletions.
7 changes: 4 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
12 changes: 7 additions & 5 deletions src/bios/bpb.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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";

Expand Down Expand Up @@ -295,9 +295,11 @@ impl BootSector {
let mut remainder: Vec<u8> = 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,
Expand Down Expand Up @@ -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]
Expand All @@ -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]
Expand Down
1 change: 1 addition & 0 deletions src/fs/fat/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = vec![0;sec_size];
let f6: Vec<u8> = vec![0xf6;sec_size];
for lsec in 0..self.boot_sector.tot_sec() as usize {
Expand Down
16 changes: 3 additions & 13 deletions src/img/imd.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,29 +111,18 @@ impl Track {
(super::FluxCode::MFM,super::DataRate::R500Kbps) => Mode::Mfm500Kbps,
_ => panic!("unhandled track mode")
};
let default_map: Vec<u8> = (1..layout.sectors[0] as u8 + 1).collect();
let sector_map: Vec<u8> = 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<u8> = Vec::new();
let head_map: Vec<u8> = match *layout {
Expand Down Expand Up @@ -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<Track> = Vec::new();
Expand Down
3 changes: 2 additions & 1 deletion src/img/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,8 @@ pub enum NibbleCode {
pub enum DataRate {
R250Kbps,
R300Kbps,
R500Kbps
R500Kbps,
R1000Kbps
}

#[derive(PartialEq,Eq,Clone,Copy)]
Expand Down
12 changes: 6 additions & 6 deletions src/img/names.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down Expand Up @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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 {
Expand All @@ -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.
Expand Down
57 changes: 44 additions & 13 deletions src/img/td0.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> = (1..layout.sectors[0] as u8 + 1).collect();
let sector_map: Vec<u8> = 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<u8> = match *layout {
super::names::KAYPRO4 => match track_num%2 {
Expand Down Expand Up @@ -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")
Expand All @@ -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 {
Expand Down Expand Up @@ -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<u8> = 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)?;
Expand All @@ -639,17 +636,33 @@ impl img::DiskImage for Td0 {
}
}
Ok(ans)
}
},
Block::FAT((_sec1,_secs)) => {
let secs_per_track = self.tracks[0].sectors.len();
let mut ans: Vec<u8> = 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))
}
}
fn write_block(&mut self, addr: Block, dat: &[u8]) -> STDRESULT {
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;
Expand All @@ -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))
}
}
Expand Down
40 changes: 40 additions & 0 deletions tests/cli_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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")?;
Expand Down Expand Up @@ -146,6 +168,24 @@ DIR3\s+<DIR>.*
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+<DIR>.*
\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 {
Expand Down
15 changes: 15 additions & 0 deletions tests/cli_test_mkdsk.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(())
}

0 comments on commit b952e6a

Please sign in to comment.