pub mod dsk_d13;
pub mod dsk_do;
pub mod dsk_po;
pub mod dsk_img;
pub mod dot2mg;
pub mod nib;
pub mod woz;
pub mod woz1;
pub mod woz2;
pub mod imd;
pub mod td0;
pub mod names;
pub mod meta;
pub mod tracks;
use std::str::FromStr;
use std::fmt;
use crate::bios::Block;
use crate::{STDRESULT,DYNERR};
use tracks::DiskFormat;
use a2kit_macro::DiskStructError;
#[derive(thiserror::Error,Debug)]
pub enum Error {
#[error("unknown kind of disk")]
UnknownDiskKind,
#[error("unknown image type")]
UnknownImageType,
#[error("unknown format")]
UnknownFormat,
#[error("invalid kind of disk")]
DiskKindMismatch,
#[error("geometric coordinate out of range")]
GeometryMismatch,
#[error("image size did not match the request")]
ImageSizeMismatch,
#[error("image type not compatible with request")]
ImageTypeMismatch,
#[error("error while accessing internal structures")]
InternalStructureAccess,
#[error("unable to access sector")]
SectorAccess,
#[error("sector not found")]
SectorNotFound,
#[error("unable to access track")]
TrackAccess,
#[error("track request out of range")]
TrackNotFound,
#[error("track is unformatted")]
BlankTrack,
#[error("metadata mismatch")]
MetadataMismatch,
#[error("wrong context for this request")]
BadContext,
#[error("invalid byte while decoding")]
InvalidByte,
#[error("bad checksum found in a sector")]
BadChecksum,
#[error("could not find bit pattern")]
BitPatternNotFound,
#[error("nibble type appeared in wrong context")]
NibbleType,
#[error("track lies outside expected zones")]
UnexpectedZone
}
#[derive(Clone,Copy,PartialEq)]
pub enum Track {
Num(usize),
CH((usize, usize)),
Motor((usize, usize)),
}
pub struct SectorHood {
vol: u8,
cyl: u8,
head: u8,
aux: u8
}
#[derive(Clone,PartialEq)]
pub enum Sector {
Num(usize),
Addr((usize,Vec<u8>))
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub enum FluxCode {
None,
FM,
GCR,
MFM
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub enum FieldCode {
None,
WOZ((usize,usize)),
G64((usize,usize)),
IBM((usize,usize))
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub struct BlockLayout {
block_size: usize,
block_count: usize
}
pub struct SolvedTrack {
flux_code: FluxCode,
addr_code: FieldCode,
data_code: FieldCode,
speed_kbps: usize,
density: Option<f64>,
addr_type: String,
addr_mask: [u8;6],
addr_map: Vec<[u8;6]>,
size_map: Vec<usize>
}
pub enum TrackSolution {
Blank,
Unsolved,
Solved(SolvedTrack)
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub struct TrackLayout {
cylinders: [usize;5],
sides: [usize;5],
sectors: [usize;5],
sector_size: [usize;5],
flux_code: [FluxCode;5],
addr_code: [FieldCode;5],
data_code: [FieldCode;5],
speed_kbps: [usize;5]
}
#[derive(PartialEq,Eq,Clone,Copy)]
pub enum DiskKind {
Unknown,
LogicalBlocks(BlockLayout),
LogicalSectors(TrackLayout),
D3(TrackLayout),
D35(TrackLayout),
D525(TrackLayout),
D8(TrackLayout)
}
#[derive(PartialEq,Clone,Copy)]
pub enum DiskImageType {
D13,
DO,
PO,
IMG,
WOZ1,
WOZ2,
IMD,
DOT2MG,
NIB,
TD0,
DOT86F,
D64,
G64,
MFI,
MFM,
HFE,
}
impl TrackLayout {
pub fn track_count(&self) -> usize {
let mut ans = 0;
for i in 0..5 {
ans += self.cylinders[i] * self.sides[i];
}
ans
}
pub fn sides(&self) -> usize {
*self.sides.iter().max().unwrap()
}
pub fn zones(&self) -> usize {
for i in 0..5 {
if self.cylinders[i]==0 {
return i;
}
}
5
}
pub fn zone(&self,track_num: usize) -> usize {
let mut tcount: [usize;5] = [0;5];
tcount[0] = self.cylinders[0] * self.sides[0];
for i in 1..5 {
tcount[i] = tcount[i-1] + self.cylinders[i] * self.sides[i];
}
match track_num {
n if n < tcount[0] => 0,
n if n < tcount[1] => 1,
n if n < tcount[2] => 2,
n if n < tcount[3] => 3,
_ => 4
}
}
pub fn byte_capacity(&self) -> usize {
let mut ans = 0;
for i in 0..5 {
ans += self.cylinders[i] * self.sides[i] * self.sectors[i] * self.sector_size[i];
}
ans
}
}
impl fmt::Display for TrackLayout {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
let mut cyls = 0;
for c in self.cylinders {
cyls += c;
}
write!(f,"{}/{}/{}/{}",cyls,self.sides(),self.sectors[0],self.sector_size[0])
}
}
impl fmt::Display for DiskKind {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
DiskKind::LogicalBlocks(lay) => write!(f,"Logical disk, {} blocks",lay.block_count),
DiskKind::LogicalSectors(lay) => write!(f,"Logical disk, {}",lay),
names::A2_400_KIND => write!(f,"Apple 3.5 inch 400K"),
names::A2_800_KIND => write!(f,"Apple 3.5 inch 800K"),
names::A2_DOS32_KIND => write!(f,"Apple 5.25 inch 13 sector"),
names::A2_DOS33_KIND => write!(f,"Apple 5.25 inch 16 sector"),
DiskKind::D3(lay) => write!(f,"3.0 inch {}",lay),
DiskKind::D35(lay) => write!(f,"3.5 inch {}",lay),
DiskKind::D525(lay) => write!(f,"5.25 inch {}",lay),
DiskKind::D8(lay) => write!(f,"8 inch {}",lay),
DiskKind::Unknown => write!(f,"unknown")
}
}
}
impl fmt::Display for FieldCode {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FieldCode::WOZ((x,y)) => write!(f,"{}&{}",x,y),
FieldCode::G64((x,y)) => write!(f,"G64-{}:{}",x,y),
FieldCode::IBM((x,y)) => write!(f,"IBM-{}:{}",x,y),
FieldCode::None => write!(f,"none")
}
}
}
impl FromStr for FieldCode {
type Err = Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
"4&4" => Ok(FieldCode::WOZ((4,4))),
"5&3" => Ok(FieldCode::WOZ((5,3))),
"6&2" => Ok(FieldCode::WOZ((6,2))),
"G64-5:4" => Ok(FieldCode::G64((5,4))),
"IBM-5:4" => Ok(FieldCode::IBM((5,4))),
"none" => Ok(FieldCode::None),
_ => Err(Error::MetadataMismatch)
}
}
}
impl FromStr for FluxCode {
type Err = Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
"FM" => Ok(FluxCode::FM),
"MFM" => Ok(FluxCode::MFM),
"GCR" => Ok(FluxCode::GCR),
"none" => Ok(FluxCode::None),
_ => Err(Error::MetadataMismatch)
}
}
}
impl fmt::Display for FluxCode {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self {
FluxCode::FM => write!(f,"FM"),
FluxCode::MFM => write!(f,"MFM"),
FluxCode::GCR => write!(f,"GCR"),
FluxCode::None => write!(f,"none")
}
}
}
impl FromStr for DiskKind {
type Err = Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
"8in-ibm-sssd" => Ok(names::IBM_CPM1_KIND),
"8in-trs80-ssdd" => Ok(names::TRS80_M2_CPM_KIND),
"8in-nabu-dsdd" => Ok(names::NABU_CPM_KIND),
"5.25in-ibm-ssdd8" => Ok(Self::D525(names::IBM_SSDD_8)),
"5.25in-ibm-ssdd9" => Ok(Self::D525(names::IBM_SSDD_9)),
"5.25in-ibm-dsdd8" => Ok(Self::D525(names::IBM_DSDD_8)),
"5.25in-ibm-dsdd9" => Ok(Self::D525(names::IBM_DSDD_9)),
"5.25in-ibm-ssqd" => Ok(Self::D525(names::IBM_SSQD)),
"5.25in-ibm-dsqd" => Ok(Self::D525(names::IBM_DSQD)),
"5.25in-ibm-dshd" => Ok(Self::D525(names::IBM_DSHD)),
"5.25in-osb-sssd" => Ok(names::OSBORNE1_SD_KIND),
"5.25in-osb-ssdd" => Ok(names::OSBORNE1_DD_KIND),
"5.25in-kay-ssdd" => Ok(names::KAYPROII_KIND),
"5.25in-kay-dsdd" => Ok(names::KAYPRO4_KIND),
"5.25in-apple-13" => Ok(names::A2_DOS32_KIND),
"5.25in-apple-16" => Ok(names::A2_DOS33_KIND),
"3.5in-apple-400" => Ok(names::A2_400_KIND),
"3.5in-apple-800" => Ok(names::A2_800_KIND),
"3.5in-ibm-720" => Ok(Self::D35(names::IBM_720)),
"3.5in-ibm-1440" => Ok(Self::D35(names::IBM_1440)),
"3.5in-ibm-2880" => Ok(Self::D35(names::IBM_2880)),
"3in-amstrad-ssdd" => Ok(names::AMSTRAD_SS_KIND),
"hdmax" => Ok(names::A2_HD_MAX),
_ => Err(Error::UnknownDiskKind)
}
}
}
impl FromStr for DiskImageType {
type Err = Error;
fn from_str(s: &str) -> Result<Self,Self::Err> {
match s {
"d13" => Ok(Self::D13),
"do" => Ok(Self::DO),
"po" => Ok(Self::PO),
"img" => Ok(Self::IMG),
"woz1" => Ok(Self::WOZ1),
"woz2" => Ok(Self::WOZ2),
"imd" => Ok(Self::IMD),
"2mg" => Ok(Self::DOT2MG),
"2img" => Ok(Self::DOT2MG),
"nib" => Ok(Self::NIB),
"td0" => Ok(Self::TD0),
_ => Err(Error::UnknownImageType)
}
}
}
impl fmt::Display for DiskImageType {
fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::D13 => write!(f,"d13"),
Self::DO => write!(f,"do"),
Self::PO => write!(f,"po"),
Self::IMG => write!(f,"img"),
Self::WOZ1 => write!(f,"woz1"),
Self::WOZ2 => write!(f,"woz2"),
Self::IMD => write!(f,"imd"),
Self::DOT2MG => write!(f,"2mg"),
Self::NIB => write!(f,"nib"),
Self::TD0 => write!(f,"td0"),
Self::D64 => write!(f,"d64"),
Self::DOT86F => write!(f,"86f"),
Self::G64 => write!(f,"g64"),
Self::HFE => write!(f,"hfe"),
Self::MFM => write!(f,"mfm"),
Self::MFI => write!(f,"mfi")
}
}
}
pub trait DiskImage {
fn track_count(&self) -> usize;
fn end_track(&self) -> usize;
fn num_heads(&self) -> usize;
fn motor_steps_per_cyl(&self) ->usize {
1
}
fn get_rz(&self,trk: Track) -> Result<[usize;2],DYNERR> {
let msc = self.motor_steps_per_cyl();
let ans = match trk {
Track::Num(t) => [t/self.num_heads(),t%self.num_heads()],
Track::CH((c,h)) => [c,h],
Track::Motor((m,h)) => [(m+msc/4)/msc,h]
};
Ok(ans)
}
fn get_track(&self,trk: Track) -> Result<usize,DYNERR> {
let msc = self.motor_steps_per_cyl();
let ans = match trk {
Track::Num(t) => t,
Track::CH((c,h)) => c*self.num_heads() + h,
Track::Motor((m,h)) => ((m+msc/4)/msc)*self.num_heads() + h
};
Ok(ans)
}
fn get_rzq(&self,trk: Track,sec: Sector) -> Result<[usize;3],DYNERR> {
let [c,h] = self.get_rz(trk)?;
let s = match sec {
Sector::Num(s) => s,
Sector::Addr((_,addr)) => {
match self.kind() {
names::A2_400_KIND | names::A2_800_KIND => addr[1] as usize,
_ => addr[2] as usize
}
}
};
Ok([c,h,s])
}
fn nominal_capacity(&self) -> Option<usize>;
fn actual_capacity(&mut self) -> Result<usize,DYNERR>;
fn what_am_i(&self) -> DiskImageType;
fn file_extensions(&self) -> Vec<String>;
fn kind(&self) -> DiskKind;
fn change_kind(&mut self,kind: DiskKind);
fn change_format(&mut self,_fmt: DiskFormat) -> STDRESULT {
Err(Box::new(Error::ImageTypeMismatch))
}
fn change_method(&mut self,_method: tracks::Method) {
}
fn from_bytes(buf: &[u8]) -> Result<Self,DiskStructError> where Self: Sized;
fn to_bytes(&mut self) -> Vec<u8>;
fn read_block(&mut self,addr: Block) -> Result<Vec<u8>,DYNERR>;
fn write_block(&mut self, addr: Block, dat: &[u8]) -> STDRESULT;
fn read_sector(&mut self,trk: Track,sec: Sector) -> Result<Vec<u8>,DYNERR>;
fn write_sector(&mut self,trk: Track,sec: Sector,dat: &[u8]) -> STDRESULT;
fn get_track_buf(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR>;
fn set_track_buf(&mut self,trk: Track,dat: &[u8]) -> STDRESULT;
fn get_track_solution(&mut self,trk: Track) -> Result<TrackSolution,DYNERR>;
fn get_track_nibbles(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR>;
fn display_track(&self,bytes: &[u8]) -> String;
fn get_metadata(&self,indent: Option<u16>) -> String {
let mut root = json::JsonValue::new_object();
let typ = self.what_am_i().to_string();
root[typ] = json::JsonValue::new_object();
if let Some(spaces) = indent {
json::stringify_pretty(root,spaces)
} else {
json::stringify(root)
}
}
fn put_metadata(&mut self,key_path: &Vec<String>, _val: &json::JsonValue) -> STDRESULT {
meta::test_metadata(key_path,self.what_am_i())
}
fn export_geometry(&mut self,indent: Option<u16>) -> Result<String,DYNERR> {
let pkg = package_string(&self.kind());
let mut track_sols = Vec::new();
for trk in 0..self.end_track() {
log::trace!("solve track {}",trk);
let sol = self.get_track_solution(Track::Num(trk))?;
let [c,h] = self.get_rz(Track::Num(trk))?;
track_sols.push((c as f64,h,sol));
}
geometry_json(pkg,track_sols,self.end_track(),self.num_heads(),self.motor_steps_per_cyl(),indent)
}
fn export_format(&self,_indent: Option<u16>) -> Result<String,DYNERR> {
Err(Box::new(Error::UnknownFormat))
}
}
fn solved_track_json(sol: SolvedTrack) -> Result<json::JsonValue,DYNERR> {
let mut ans = json::JsonValue::new_object();
ans["flux_code"] = match sol.flux_code {
FluxCode::None => json::JsonValue::Null,
f => json::JsonValue::String(f.to_string())
};
ans["addr_code"] = match sol.addr_code {
FieldCode::None => json::JsonValue::Null,
n => json::JsonValue::String(n.to_string())
};
ans["nibble_code"] = match sol.data_code {
FieldCode::None => json::JsonValue::Null,
n => json::JsonValue::String(n.to_string())
};
ans["speed_kbps"] = json::JsonValue::Number(sol.speed_kbps.into());
ans["density"] = match sol.density {
Some(val) => json::JsonValue::Number(val.into()),
None => json::JsonValue::Null
};
ans["addr_map"] = json::JsonValue::new_array();
for addr in sol.addr_map {
ans["addr_map"].push(json::JsonValue::String(hex::encode_upper(&addr[0..sol.addr_type.len()])))?;
}
ans["size_map"] = json::JsonValue::new_array();
for size in sol.size_map {
ans["size_map"].push(size)?;
}
ans["addr_type"] = json::JsonValue::String(sol.addr_type);
ans["addr_mask"] = json::JsonValue::new_array();
for by in sol.addr_mask {
ans["addr_mask"].push(by)?;
}
Ok(ans)
}
fn geometry_json(pkg: String,desc: Vec<(f64,usize,TrackSolution)>,cylinders: usize,heads: usize,width: usize,indent: Option<u16>) -> Result<String,DYNERR> {
let mut root = json::JsonValue::new_object();
root["package"] = json::JsonValue::String(pkg);
let mut trk_ary = json::JsonValue::new_array();
let mut blank_track_count = 0;
let mut solved_track_count = 0;
let mut unsolved_track_count = 0;
let mut last_blank_track: Option<usize> = None;
let mut last_solved_track: Option<usize> = None;
let mut last_unsolved_track: Option<usize> = None;
let mut idx = 0;
for (fcyl,head,sol) in desc {
let mut trk_obj = json::JsonValue::new_object();
trk_obj["cylinder"] = json::JsonValue::Number(fcyl.into());
trk_obj["head"] = json::JsonValue::Number(head.into());
let ignore = match sol {
TrackSolution::Blank => {
if fcyl < cylinders as f64 {
blank_track_count += 1;
last_blank_track = Some(idx);
}
fcyl >= cylinders as f64
},
TrackSolution::Unsolved => {
unsolved_track_count += 1;
last_unsolved_track = Some(idx);
false
},
TrackSolution::Solved(_) => {
solved_track_count += 1;
last_solved_track = Some(idx);
false
}
};
trk_obj["solution"] = match sol {
TrackSolution::Blank => json::JsonValue::String("blank".to_string()),
TrackSolution::Unsolved => json::JsonValue::String("unsolved".to_string()),
TrackSolution::Solved(sol) => solved_track_json(sol)?
};
if !ignore {
trk_ary.push(trk_obj)?;
}
idx += 1;
}
root["summary"] = json::JsonValue::new_object();
root["summary"]["cylinders"] = json::JsonValue::Number(cylinders.into());
root["summary"]["heads"] = json::JsonValue::Number(heads.into());
root["summary"]["blank_tracks"] = json::JsonValue::Number(blank_track_count.into());
root["summary"]["solved_tracks"] = json::JsonValue::Number(solved_track_count.into());
root["summary"]["unsolved_tracks"] = json::JsonValue::Number(unsolved_track_count.into());
root["summary"]["last_blank_track"] = match last_blank_track {
Some(t) => json::JsonValue::Number(t.into()),
None => json::JsonValue::Null
};
root["summary"]["last_solved_track"] = match last_solved_track {
Some(t) => json::JsonValue::Number(t.into()),
None => json::JsonValue::Null
};
root["summary"]["last_unsolved_track"] = match last_unsolved_track {
Some(t) => json::JsonValue::Number(t.into()),
None => json::JsonValue::Null
};
root["summary"]["steps_per_cyl"] = json::JsonValue::Number(width.into());
if trk_ary.len()==0 {
root["tracks"] = json::JsonValue::Null;
} else {
root["tracks"] = trk_ary;
}
if let Some(spaces) = indent {
Ok(json::stringify_pretty(root,spaces))
} else {
Ok(json::stringify(root))
}
}
pub fn is_dos_size(dsk: &Vec<u8>,allowed_track_counts: &Vec<usize>,sectors: usize) -> STDRESULT {
let bytes = dsk.len();
for tracks in allowed_track_counts {
if bytes==tracks*sectors*256 {
return Ok(());
}
}
log::info!("image size was {}",bytes);
return Err(Box::new(Error::ImageSizeMismatch));
}
pub fn quantize_block(src: &[u8],quantum: usize) -> Vec<u8> {
let mut padded: Vec<u8> = Vec::new();
for i in 0..quantum {
if i<src.len() {
padded.push(src[i])
} else {
padded.push(0);
}
}
return padded;
}
pub fn package_string(kind: &DiskKind) -> String {
match kind {
DiskKind::D3(_) => "3".to_string(),
DiskKind::D35(_) => "3.5".to_string(),
DiskKind::D525(_) => "5.25".to_string(),
DiskKind::D8(_) => "8".to_string(),
DiskKind::LogicalBlocks(_) => "logical".to_string(),
DiskKind::LogicalSectors(_) => "logical".to_string(),
DiskKind::Unknown => "unknown".to_string()
}
}
fn highest_bit(mut val: usize) -> u8 {
let mut ans = 0;
while val > 0 {
ans += 1;
val = val >> 1;
}
ans
}
pub fn append_ibm_crc(addr: [u8;4],maybe_sync: Option<[u8;4]>) -> [u8;6]
{
let mut buf = vec![];
match maybe_sync {
Some(sync) => buf.append(&mut sync.to_vec()),
None => buf.append(&mut vec![0xa1,0xa1,0xa1,0xfe])
};
buf.append(&mut addr.to_vec());
let buf = [[0xa1,0xa1,0xa1,0xfe],[addr[0],addr[1],addr[2],addr[3]]].concat();
let mut crc: u16 = 0xffff;
for i in 0..buf.len() {
crc ^= (buf[i] as u16) << 8;
for _bit in 0..8 {
crc = (crc << 1) ^ match crc & 0x8000 { 0 => 0, _ => 0x1021 };
}
}
let be = u16::to_be_bytes(crc);
[addr[0],addr[1],addr[2],addr[3],be[0],be[1]]
}