use clap;
use std::str::FromStr;
use crate::bios::{bpb,dpb};
use crate::fs::{DiskFS,cpm,dos3x,prodos,pascal,fat};
use crate::img;
use crate::img::{Track,DiskKind,DiskImage,DiskImageType,names};
use crate::img::tracks::{DiskFormat};
use super::CommandError;
use crate::{STDRESULT,DYNERR};
const RCH: &str = "unreachable was reached";
const BOOT_MESS: &str = "omit boot flag; for this OS you will need to copy boot files after formatting";
const BOOT_MESS_CPM: &str = "omit boot flag; for this OS you will need to copy reserved tracks after formatting";
const BOOT_MESS_FAT: &str = "omit boot flag; for this OS copy reserved sectors and boot files after formatting";
macro_rules! ibm_patterns {
() => {
DiskKind::D525(names::IBM_SSDD_8) |
DiskKind::D525(names::IBM_SSDD_9) |
DiskKind::D525(names::IBM_DSDD_8) |
DiskKind::D525(names::IBM_DSDD_9) |
DiskKind::D525(names::IBM_SSQD) |
DiskKind::D525(names::IBM_DSQD) |
DiskKind::D525(names::IBM_DSHD) |
DiskKind::D35(names::IBM_720) |
DiskKind::D35(names::IBM_1440) |
DiskKind::D35(names::IBM_2880)
};
}
macro_rules! cpm_patterns {
() => {
names::IBM_CPM1_KIND |
names::OSBORNE1_SD_KIND |
names::OSBORNE1_DD_KIND |
names::KAYPROII_KIND |
names::KAYPRO4_KIND |
names::TRS80_M2_CPM_KIND |
names::NABU_CPM_KIND |
names::AMSTRAD_SS_KIND
};
}
fn verify_mkimage(img_typ: &DiskImageType,maybe_vol: Option<&String>,maybe_wrap: Option<&String>,fmt: &Option<DiskFormat>,flux: &Vec<Track>) -> Result<u8,DYNERR> {
let vol = match maybe_vol {
Some(vstr) => match u8::from_str_radix(vstr,10) {
Ok(v) => v,
_ => 254
},
_ => 254
};
match (img_typ,maybe_wrap) {
(DiskImageType::DOT2MG,None) => {
log::error!("selected image type requires the `--wrap` option");
return Err(Box::new(CommandError::InvalidCommand))
},
(DiskImageType::DOT2MG,Some(_)) => {},
(_,None) => {},
_ => {
log::error!("omit the `--wrap` option for this image type");
return Err(Box::new(CommandError::InvalidCommand))
}
}
match (img_typ,flux.len()>0) {
(DiskImageType::WOZ2,_) => {},
(_,false) => {},
_ => {
log::error!("selected image type does not support flux tracks");
return Err(Box::new(CommandError::InvalidCommand))
}
}
match (img_typ,fmt) {
(_,None) => {},
(DiskImageType::WOZ1,Some(_)) => {},
(DiskImageType::WOZ2,Some(_)) => {},
_ => {
log::error!("unable to use proprietary tracks for this image type");
return Err(Box::new(CommandError::InvalidCommand))
}
}
Ok(vol)
}
fn mkimage(img_typ: &DiskImageType,maybe_wrap: Option<&String>,maybe_vol: Option<&String>,kind: &DiskKind,fmt: Option<DiskFormat>,flux: Vec<Track>) -> Result<Box<dyn DiskImage>,DYNERR> {
let vol = verify_mkimage(img_typ,maybe_vol,maybe_wrap,&fmt,&flux)?;
match (img_typ,*kind) {
(DiskImageType::D13,names::A2_DOS32_KIND) => Ok(Box::new(img::dsk_d13::D13::create(35))),
(DiskImageType::DO,names::A2_DOS33_KIND) => Ok(Box::new(img::dsk_do::DO::create(35,16))),
(DiskImageType::WOZ1,names::A2_DOS32_KIND) => Ok(Box::new(img::woz1::Woz1::create(vol,*kind,fmt)?)),
(DiskImageType::WOZ1,names::A2_DOS33_KIND) => Ok(Box::new(img::woz1::Woz1::create(vol,*kind,fmt)?)),
(DiskImageType::WOZ2,names::A2_DOS32_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind,fmt,flux)?)),
(DiskImageType::WOZ2,names::A2_DOS33_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind,fmt,flux)?)),
(DiskImageType::WOZ2,names::A2_400_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind,fmt,flux)?)),
(DiskImageType::WOZ2,names::A2_800_KIND) => Ok(Box::new(img::woz2::Woz2::create(vol,*kind,fmt,flux)?)),
(DiskImageType::PO,names::A2_DOS33_KIND) => Ok(Box::new(img::dsk_po::PO::create(280))),
(DiskImageType::PO,names::A2_400_KIND) => Ok(Box::new(img::dsk_po::PO::create(800))),
(DiskImageType::PO,names::A2_800_KIND) => Ok(Box::new(img::dsk_po::PO::create(1600))),
(DiskImageType::PO,names::A2_HD_MAX) => Ok(Box::new(img::dsk_po::PO::create(65535))),
(DiskImageType::DOT2MG,names::A2_DOS33_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_400_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_800_KIND) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::DOT2MG,names::A2_HD_MAX) => img::dot2mg::Dot2mg::create(vol,*kind,maybe_wrap),
(DiskImageType::NIB,names::A2_DOS32_KIND) => Ok(Box::new(img::nib::Nib::create(vol,*kind)?)),
(DiskImageType::NIB,names::A2_DOS33_KIND) => Ok(Box::new(img::nib::Nib::create(vol,*kind)?)),
(DiskImageType::IMD,cpm_patterns!()) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::TD0,cpm_patterns!()) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::IMD,ibm_patterns!()) => Ok(Box::new(img::imd::Imd::create(*kind))),
(DiskImageType::TD0,ibm_patterns!()) => Ok(Box::new(img::td0::Td0::create(*kind))),
(DiskImageType::IMG,ibm_patterns!()) => Ok(Box::new(img::dsk_img::Img::create(*kind))),
_ => {
log::error!("pairing of image type and disk kind is not supported");
Err(Box::new(CommandError::UnsupportedItemType))
}
}
}
fn mkblank(img_typ: &DiskImageType,kind: &DiskKind,_maybe_wrap: Option<&String>) -> Result<Box<dyn DiskImage>,DYNERR> {
return match (img_typ,*kind) {
(DiskImageType::WOZ1,names::A2_DOS32_KIND) => Ok(Box::new(img::woz1::Woz1::blank(*kind))),
(DiskImageType::WOZ1,names::A2_DOS33_KIND) => Ok(Box::new(img::woz1::Woz1::blank(*kind))),
(DiskImageType::WOZ2,names::A2_DOS32_KIND) => Ok(Box::new(img::woz2::Woz2::blank(*kind))),
(DiskImageType::WOZ2,names::A2_DOS33_KIND) => Ok(Box::new(img::woz2::Woz2::blank(*kind))),
(DiskImageType::WOZ2,names::A2_400_KIND) => Ok(Box::new(img::woz2::Woz2::blank(*kind))),
(DiskImageType::WOZ2,names::A2_800_KIND) => Ok(Box::new(img::woz2::Woz2::blank(*kind))),
_ => {
log::error!("this type of image cannot be blank, maybe you want empty");
Err(Box::new(CommandError::UnsupportedItemType))
}
};
}
fn mkdos3x(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if img.nominal_capacity().is_none() {
log::error!("disk image did not support nominal capacity check");
return Err(Box::new(CommandError::UnsupportedFormat));
}
if img.nominal_capacity().unwrap()!=35*13*256 && img.nominal_capacity().unwrap()!=35*16*256 {
log::error!("disk image capacity {} not consistent with DOS 3.x",img.nominal_capacity().unwrap());
return Err(Box::new(CommandError::OutOfRange));
}
let kind = img.kind(); match (kind,img.what_am_i()) {
(_,DiskImageType::PO) => {
log::error!("attempt to create ProDOS ordered DOS disk");
return Err(Box::new(CommandError::UnsupportedFormat));
},
(DiskKind::LogicalSectors(img::names::A2_DOS32),DiskImageType::DO) |
(DiskKind::D525(img::names::A2_DOS32),DiskImageType::DO) => {
log::error!("DOS 3.2 cannot use DO image type, use D13");
return Err(Box::new(CommandError::UnsupportedFormat))
},
_ => {}
}
if vol==None {
log::error!("DOS 3.x requires volume number");
return Err(Box::new(CommandError::InvalidCommand));
}
match u8::from_str_radix(vol.unwrap(), 10) {
Ok(v) if v>=1 || v<=254 => {
if boot && v!=254 {
log::error!("we can only add the boot tracks if volume number is 254");
return Err(Box::new(CommandError::UnsupportedItemType));
}
let mut disk = dos3x::Disk::from_img(img)?;
match kind {
DiskKind::LogicalSectors(img::names::A2_DOS32) => disk.init32(v,boot)?,
DiskKind::D525(img::names::A2_DOS32) => disk.init32(v,boot)?,
DiskKind::LogicalSectors(img::names::A2_DOS33) => disk.init33(v,boot)?,
DiskKind::D525(img::names::A2_DOS33) => disk.init33(v,boot)?,
_ => {
log::error!("disk incompatible with DOS 3.x");
return Err(Box::new(CommandError::UnsupportedFormat));
}
}
return Ok(disk.get_img().to_bytes());
},
_ => {
log::error!("volume must be from 1 to 254");
return Err(Box::new(CommandError::OutOfRange));
}
}
}
fn mkprodos(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
log::error!("{}",BOOT_MESS);
return Err(Box::new(CommandError::UnsupportedItemType));
}
let floppy = match img.kind() {
DiskKind::D35(_) => true,
DiskKind::D525(_) => true,
DiskKind::D8(_) => true,
_ => false
};
if let Some(vol_name) = vol {
let mut disk = prodos::Disk::from_img(img)?;
disk.format(vol_name,floppy,None)?;
return Ok(disk.get_img().to_bytes());
} else {
log::error!("prodos fs requires volume name");
return Err(Box::new(CommandError::InvalidCommand));
}
}
fn mkpascal(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
log::error!("{}",BOOT_MESS);
return Err(Box::new(CommandError::UnsupportedItemType));
}
if let Some(vol_name) = vol {
let mut disk = pascal::Disk::from_img(img)?;
disk.format(vol_name,0xee,None)?;
return Ok(disk.get_img().to_bytes());
} else {
log::error!("pascal fs requires volume name");
return Err(Box::new(CommandError::InvalidCommand));
}
}
fn mkcpm(vol: Option<&String>,boot: bool,kind: &DiskKind,img: Box<dyn DiskImage>,vers: u8) -> Result<Vec<u8>,DYNERR> {
if boot {
log::error!("{}",BOOT_MESS_CPM);
return Err(Box::new(CommandError::UnsupportedItemType));
}
if vers<3 && vol.is_some() {
log::warn!("volume name inapplicable for CP/M version < 3");
}
let (vol_name,time,cpm_vers) = match vers {
3 => match vol {
Some(nm) => (nm.as_str(),Some(chrono::Local::now().naive_local()),[3,1,0]),
None => ("",Some(chrono::Local::now().naive_local()),[3,1,0])
},
2 => ("",None,[2,2,3]),
_ => panic!("unexpected CP/M version")
};
let mut disk = cpm::Disk::from_img(img,dpb::DiskParameterBlock::create(&kind),cpm_vers)?;
disk.format(vol_name,time)?;
Ok(disk.get_img().to_bytes())
}
fn mkfat(vol: Option<&String>,boot: bool,img: Box<dyn DiskImage>) -> Result<Vec<u8>,DYNERR> {
if boot {
log::error!("{}",BOOT_MESS_FAT);
return Err(Box::new(CommandError::UnsupportedItemType));
}
let boot_sector = bpb::BootSector::create(&img.kind())?;
let mut disk = fat::Disk::from_img(img,Some(boot_sector))?;
let vol_name = match vol {
Some(nm) => nm.as_str(),
None => ""
};
disk.format(vol_name,None)?;
Ok(disk.get_img().to_bytes())
}
pub fn mkdsk(cmd: &clap::ArgMatches) -> STDRESULT {
let dest_path= cmd.get_one::<String>("dimg").expect(RCH);
let dest_path_abstract = std::path::Path::new(dest_path);
if let Some(parent) = std::path::Path::parent(dest_path_abstract) {
if parent.to_string_lossy().len()>0 {
match std::path::Path::try_exists(parent) {
Ok(true) => {},
Ok(false) => {
log::error!("destination directory does not exist ({})",parent.to_string_lossy());
return Err(Box::new(CommandError::InvalidCommand));
},
Err(e) => {
log::error!("problem with this destination path");
return Err(Box::new(e))
}
}
}
}
match std::path::Path::try_exists(dest_path_abstract) {
Ok(true) => {
log::error!("cannot overwrite existing disk image");
return Err(Box::new(CommandError::InvalidCommand));
},
Ok(false) => log::info!("destination path OK, preparing to write"),
Err(e) => {
log::error!("problem with this destination path");
return Err(Box::new(e))
}
}
let fmt = super::get_fmt(cmd)?;
let maybe_kind = cmd.get_one::<String>("kind");
let img_typ = DiskImageType::from_str(cmd.get_one::<String>("type").expect(RCH)).unwrap();
let maybe_vol = cmd.get_one::<String>("volume");
let maybe_wrap = cmd.get_one::<String>("wrap");
let maybe_os = cmd.get_one::<String>("os");
let maybe_flux = cmd.get_one::<String>("flux");
let boot = cmd.get_flag("bootable");
if boot {
log::info!("bootable requested");
}
let kind = match (maybe_kind,maybe_os) {
(Some(k),_) => DiskKind::from_str(k).unwrap(),
(None,Some(os)) => match os.as_str() {
"cpm2" | "cpm3" => names::IBM_CPM1_KIND,
"dos32" => names::A2_DOS32_KIND,
"dos33" | "prodos" | "pascal" => names::A2_DOS33_KIND,
"fat" => DiskKind::D525(names::IBM_DSDD_9),
_ => panic!("unexpected operating system format")
},
(None,None) => {
log::error!("`--kind` and `--os` cannot both be missing");
return Err(Box::new(CommandError::InvalidCommand));
}
};
let steps_per_cyl = match kind {
names::A2_DOS32_KIND | names::A2_DOS33_KIND => 4,
_ => 1
};
let flux = match maybe_flux {
Some(farg) => super::parse_track_request(farg, steps_per_cyl)?,
None => vec![]
};
let mut img = match cmd.get_flag("blank") {
true => mkblank(&img_typ,&kind,maybe_wrap)?,
false => mkimage(&img_typ,maybe_wrap,maybe_vol,&kind,fmt,flux)?, };
if let Some(fext) = dest_path.split(".").last() {
if !img.file_extensions().contains(&fext.to_string().to_lowercase()) {
log::error!("Extension was {}, should be {:?}",fext,img.file_extensions());
return Err(Box::new(CommandError::InvalidCommand));
}
} else {
log::error!("Extension missing, should be {:?}",img.file_extensions());
return Err(Box::new(CommandError::InvalidCommand));
}
let buf = match maybe_os {
Some(os) => match os.as_str() {
"cpm2" => mkcpm(maybe_vol,boot,&kind,img,2)?,
"cpm3" => mkcpm(maybe_vol,boot,&kind,img,3)?,
"dos32" => mkdos3x(maybe_vol,boot,img)?,
"dos33" => mkdos3x(maybe_vol,boot,img)?,
"prodos" => mkprodos(maybe_vol,boot,img)?,
"pascal" => mkpascal(maybe_vol,boot,img)?,
"fat" => mkfat(maybe_vol,boot,img)?,
_ => panic!("{}",RCH)
},
None => img.to_bytes() };
eprintln!("writing {} bytes",buf.len());
Ok(std::fs::write(&dest_path,&buf)?)
}