use crate::{
chs::{DiskChs, DiskChsn},
detect::chs_from_raw_size,
diskimage::{DiskDescriptor, DiskImage, RwSectorScope},
file_parsers::{FormatCaps, ParserWriteCompatibility},
io::{ReadSeek, ReadWriteSeek},
structure_parsers::system34::System34Standard,
util::get_length,
DiskCh,
DiskDataResolution,
DiskDensity,
DiskImageError,
DiskImageFileFormat,
LoadingCallback,
StandardFormat,
DEFAULT_SECTOR_SIZE,
};
use std::cmp::Ordering;
pub struct RawFormat;
impl RawFormat {
#[allow(dead_code)]
fn format() -> DiskImageFileFormat {
DiskImageFileFormat::RawSectorImage
}
pub(crate) fn extensions() -> Vec<&'static str> {
vec!["img", "ima", "dsk", "bin"]
}
pub(crate) fn capabilities() -> FormatCaps {
FormatCaps::empty()
}
pub(crate) fn detect<RWS: ReadSeek>(mut image: RWS) -> bool {
let raw_len = get_length(&mut image).map_or(0, |l| l as usize);
chs_from_raw_size(raw_len).is_some()
}
pub(crate) fn can_write(image: &DiskImage) -> ParserWriteCompatibility {
if !image.consistency.image_caps.is_empty() {
log::warn!("RAW sector images do not support capability flags.");
ParserWriteCompatibility::DataLoss
}
else {
ParserWriteCompatibility::Ok
}
}
pub(crate) fn load_image<RWS: ReadSeek>(
mut raw: RWS,
disk_image: &mut DiskImage,
_callback: Option<LoadingCallback>,
) -> Result<(), DiskImageError> {
disk_image.set_source_format(DiskImageFileFormat::RawSectorImage);
disk_image.set_resolution(DiskDataResolution::BitStream);
let raw_len = get_length(&mut raw).map_err(|_e| DiskImageError::UnknownFormat)? as usize;
let floppy_format = StandardFormat::from(raw_len);
if floppy_format == StandardFormat::Invalid {
return Err(DiskImageError::UnknownFormat);
}
else {
log::trace!("Raw::load_image(): Detected format {}", floppy_format);
}
let disk_chs = floppy_format.get_chs();
log::trace!("Raw::load_image(): Disk CHS: {}", disk_chs);
let data_rate = floppy_format.get_data_rate();
let data_encoding = floppy_format.get_encoding();
let bitcell_ct = floppy_format.get_bitcell_ct();
let rpm = floppy_format.get_rpm();
let gap3 = floppy_format.get_gap3();
raw.seek(std::io::SeekFrom::Start(0))?;
let track_size = disk_chs.s() as usize * DEFAULT_SECTOR_SIZE;
let track_ct = raw_len / track_size;
if disk_chs.c() as usize * disk_chs.h() as usize != track_ct {
log::error!("Raw::load_image(): Calculated track count does not match standard image.");
return Err(DiskImageError::UnknownFormat);
}
let track_ct_overflow = raw_len % track_size;
if track_ct_overflow != 0 {
return Err(DiskImageError::UnknownFormat);
}
let mut sector_buffer = vec![0u8; DEFAULT_SECTOR_SIZE];
for c in 0..disk_chs.c() {
for h in 0..disk_chs.h() {
log::trace!("Raw::load_image(): Adding new track: c:{} h:{}", c, h);
let new_track_idx =
disk_image.add_empty_track(DiskCh::new(c, h), data_encoding, data_rate, bitcell_ct)?;
let mut format_buffer = Vec::with_capacity(disk_chs.s() as usize);
let mut track_pattern = Vec::with_capacity(DEFAULT_SECTOR_SIZE * disk_chs.s() as usize);
log::trace!("Raw::load_image(): Formatting track with {} sectors", disk_chs.s());
for s in 1..disk_chs.s() + 1 {
let sector_chsn = DiskChsn::new(c, h, s, 2);
raw.read_exact(&mut sector_buffer)?;
track_pattern.extend(sector_buffer.clone());
format_buffer.push(sector_chsn);
}
let td = disk_image
.track_by_idx_mut(new_track_idx)
.ok_or(DiskImageError::FormatParseError)?;
td.format(System34Standard::Ibm, format_buffer, &track_pattern, gap3)?;
}
}
disk_image.descriptor = DiskDescriptor {
geometry: disk_chs.into(),
data_rate,
data_encoding,
density: DiskDensity::from(data_rate),
default_sector_size: DEFAULT_SECTOR_SIZE,
rpm: Some(rpm),
write_protect: None,
};
Ok(())
}
pub fn save_image<RWS: ReadWriteSeek>(image: &mut DiskImage, output: &mut RWS) -> Result<(), DiskImageError> {
let track_ct = match image.track_map[0].len() {
39..=50 => 40,
79..=90 => 80,
_ => {
log::error!(
"Raw::save_image(): Unsupported track count: {}",
image.track_map[0].len()
);
return Err(DiskImageError::UnsupportedFormat);
}
};
let track_sector_ct = if let Some(csc) = image.consistency.consistent_track_length {
csc as usize
}
else {
log::warn!("Raw::save_image(): Image has inconsistent sector counts per track. Data will be lost.");
let track_idx = image.track_map[0][0];
let track = &image.track_pool[track_idx];
track.get_sector_ct()
};
log::trace!("Raw::save_image(): Using {} sectors per track.", track_sector_ct);
for c in 0..track_ct {
for h in 0..image.heads() as usize {
let ti = image.track_map[h][c];
let track = &mut image.track_pool[ti];
for s in 1..(track_sector_ct + 1) {
let chs = DiskChs::new(c as u16, h as u8, s as u8);
match track.read_sector(chs, None, RwSectorScope::DataOnly, false) {
Ok(read_sector) => {
log::trace!(
"Raw::save_image(): Read {} bytes from sector: {}",
read_sector.read_buf.len(),
chs
);
let mut new_buf = read_sector.read_buf.clone();
match new_buf.len().cmp(&DEFAULT_SECTOR_SIZE) {
Ordering::Greater => {
log::warn!(
"Raw::save_image(): c:{} h:{} Sector {} is too large: {}. Truncating to {}",
c,
h,
s,
new_buf.len(),
DEFAULT_SECTOR_SIZE
);
new_buf.truncate(DEFAULT_SECTOR_SIZE);
}
Ordering::Less => {
log::warn!(
"Raw::save_image(): c:{} h:{} Sector {} is too small: {}. Padding with 0",
c,
h,
s,
new_buf.len()
);
new_buf.extend(vec![0u8; DEFAULT_SECTOR_SIZE - new_buf.len()]);
}
Ordering::Equal => {}
}
output.write_all(new_buf.as_ref())?;
}
Err(e) => {
log::error!("Raw::save_image(): Error reading c:{} h:{} s:{} err: {}", c, h, s, e);
return Err(DiskImageError::DataError);
}
}
}
}
}
Ok(())
}
}