extern crate core;
mod bit_ring;
pub mod bitstream;
mod boot_sector;
mod chs;
mod containers;
mod detect;
pub mod diskimage;
mod file_parsers;
pub mod image_builder;
mod io;
mod random;
pub mod standard_format;
pub mod structure_parsers;
pub mod util;
mod copy_protection;
pub mod flux;
mod image_writer;
mod range_check;
mod track;
#[cfg(feature = "viz")]
pub mod visualization;
use std::{
fmt,
fmt::{Display, Formatter},
hash::RandomState,
};
use thiserror::Error;
pub const MAXIMUM_SECTOR_SIZE: usize = 8192;
pub const DEFAULT_SECTOR_SIZE: usize = 512;
pub const ASCII_EOF: u8 = 0x1A;
#[allow(unused)]
type FoxHashMap<K, V, S = RandomState> = std::collections::HashMap<K, V, S>;
#[allow(unused)]
type FoxHashSet<T, S = RandomState> = std::collections::HashSet<T, S>;
pub enum LoadingStatus {
ProgressSupport(bool),
Progress(f64),
Complete,
Error,
}
type LoadingCallback = Box<dyn Fn(LoadingStatus) + Send + 'static>;
#[derive(Debug, Error)]
pub enum DiskImageError {
#[error("An IO error occurred reading or writing the disk image: {0}")]
IoError(String),
#[error("A filesystem error occurred or path not found")]
FsError,
#[error("Unknown disk image format")]
UnknownFormat,
#[error("Unsupported disk image format for requested operation")]
UnsupportedFormat,
#[error("The disk image is valid but contains incompatible disk information")]
IncompatibleImage,
#[error("The disk image format parser encountered an error")]
FormatParseError,
#[error("The disk image format parser determined the image was corrupt")]
ImageCorruptError,
#[error("The requested head or cylinder could not be found")]
SeekError,
#[error("An error occurred addressing the track bitstream")]
BitstreamError,
#[error("The requested sector ID could not be found")]
IdError,
#[error("The requested operation matched multiple sector IDs")]
UniqueIdError,
#[error("No sectors were found on the current track")]
DataError,
#[error("A CRC error was detected in the disk image")]
CrcError,
#[error("An invalid function parameter was supplied")]
ParameterError,
#[error("Write-protect status prevents writing to the disk image")]
WriteProtectError,
#[error("Flux track has not been resolved")]
ResolveError,
#[error("An error occurred reading a multi-disk archive: {0}")]
MultiDiskError(String),
}
impl From<io::Error> for DiskImageError {
fn from(err: io::Error) -> Self {
DiskImageError::IoError(err.to_string()) }
}
impl From<binrw::Error> for DiskImageError {
fn from(err: binrw::Error) -> Self {
DiskImageError::IoError(err.to_string()) }
}
#[derive(Debug, Error)]
pub enum DiskVisualizationError {
#[error("An invalid parameter was supplied")]
InvalidParameter,
#[error("The disk image is not a valid format for visualization")]
NoTracks,
}
#[repr(usize)]
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq, Hash)]
pub enum DiskDataResolution {
#[default]
MetaSector = 0,
BitStream = 1,
FluxStream = 2,
}
#[derive(Default, Copy, Clone, Debug)]
pub enum DiskDataEncoding {
#[default]
#[doc = "Frequency Modulation encoding. Used by older 8" diskettes, and duplication tracks on some 5.25" diskettes."]
Fm,
#[doc = "Modified Frequency Modulation encoding. Used by almost all 5.25" and 3.5" diskettes."]
Mfm,
#[doc = "Group Code Recording encoding. Used by Apple and Macintosh diskettes."]
Gcr,
}
impl DiskDataEncoding {
pub fn byte_size(&self) -> usize {
match self {
DiskDataEncoding::Fm => 16,
DiskDataEncoding::Mfm => 16,
DiskDataEncoding::Gcr => 0,
}
}
pub fn marker_size(&self) -> usize {
match self {
DiskDataEncoding::Fm => 64,
DiskDataEncoding::Mfm => 64,
DiskDataEncoding::Gcr => 0,
}
}
}
impl Display for DiskDataEncoding {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
DiskDataEncoding::Fm => write!(f, "FM"),
DiskDataEncoding::Mfm => write!(f, "MFM"),
DiskDataEncoding::Gcr => write!(f, "GCR"),
}
}
}
#[derive(Default, Copy, Clone, Debug)]
pub enum DiskPhysicalDimensions {
#[doc = "An 8\" Diskette"]
Dimension8,
#[default]
#[doc = "A 5.25\" Diskette"]
Dimension5_25,
#[doc = "A 3.5\" Diskette"]
Dimension3_5,
}
#[derive(Default, Copy, Clone, Debug)]
pub enum DiskDensity {
Standard,
#[default]
Double,
High,
Extended,
}
impl From<DiskDataRate> for DiskDensity {
fn from(rate: DiskDataRate) -> Self {
match rate {
DiskDataRate::Rate125Kbps(_) => DiskDensity::Standard,
DiskDataRate::Rate250Kbps(_) => DiskDensity::Double,
DiskDataRate::Rate500Kbps(_) => DiskDensity::High,
DiskDataRate::Rate1000Kbps(_) => DiskDensity::Extended,
_ => DiskDensity::Double,
}
}
}
impl Display for DiskDensity {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
DiskDensity::Standard => write!(f, "Standard"),
DiskDensity::Double => write!(f, "Double"),
DiskDensity::High => write!(f, "High"),
DiskDensity::Extended => write!(f, "Extended"),
}
}
}
impl DiskDensity {
pub fn bitcells(&self, rpm: Option<DiskRpm>) -> Option<usize> {
match (self, rpm) {
(DiskDensity::Standard, _) => Some(50_000),
(DiskDensity::Double, _) => Some(100_000),
(DiskDensity::High, Some(DiskRpm::Rpm360)) => Some(166_666),
(DiskDensity::High, Some(DiskRpm::Rpm300) | None) => Some(200_000),
(DiskDensity::Extended, _) => Some(400_000),
}
}
pub fn base_clock(&self, rpm: Option<DiskRpm>) -> f64 {
match (self, rpm) {
(DiskDensity::Standard, _) => 4e-6,
(DiskDensity::Double, None | Some(DiskRpm::Rpm300)) => 2e-6,
(DiskDensity::Double, Some(DiskRpm::Rpm360)) => 1.666e-6,
(DiskDensity::High, _) => 1e-6,
(DiskDensity::Extended, _) => 5e-7,
}
}
pub fn from_base_clock(clock: f64) -> Option<DiskDensity> {
match clock {
0.375e-6..0.625e-6 => Some(DiskDensity::Extended),
0.75e-6..1.25e-6 => Some(DiskDensity::High),
1.5e-6..2.5e-6 => Some(DiskDensity::Double),
_ => None,
}
}
}
#[derive(Copy, Clone, Debug)]
pub enum DiskDataRate {
RateNonstandard(u32),
Rate125Kbps(f64),
Rate250Kbps(f64),
Rate300Kbps(f64),
Rate500Kbps(f64),
Rate1000Kbps(f64),
}
impl Default for DiskDataRate {
fn default() -> Self {
DiskDataRate::Rate250Kbps(1.0)
}
}
impl From<DiskDataRate> for u32 {
fn from(rate: DiskDataRate) -> Self {
match rate {
DiskDataRate::Rate125Kbps(f) => (125_000.0 * f) as u32,
DiskDataRate::Rate250Kbps(f) => (250_000.0 * f) as u32,
DiskDataRate::Rate300Kbps(f) => (300_000.0 * f) as u32,
DiskDataRate::Rate500Kbps(f) => (500_000.0 * f) as u32,
DiskDataRate::Rate1000Kbps(f) => (1_000_000.0 * f) as u32,
DiskDataRate::RateNonstandard(rate) => rate,
}
}
}
impl From<u32> for DiskDataRate {
fn from(rate: u32) -> Self {
match rate {
93_750..143_750 => DiskDataRate::Rate125Kbps(rate as f64 / 125_000.0),
212_000..271_000 => DiskDataRate::Rate250Kbps(rate as f64 / 250_000.0),
271_000..345_000 => DiskDataRate::Rate300Kbps(rate as f64 / 300_000.0),
425_000..575_000 => DiskDataRate::Rate500Kbps(rate as f64 / 500_000.0),
850_000..1_150_000 => DiskDataRate::Rate1000Kbps(rate as f64 / 1_000_000.0),
_ => DiskDataRate::RateNonstandard(rate),
}
}
}
impl From<DiskDensity> for DiskDataRate {
fn from(density: DiskDensity) -> Self {
match density {
DiskDensity::Standard => DiskDataRate::Rate125Kbps(1.0),
DiskDensity::Double => DiskDataRate::Rate250Kbps(1.0),
DiskDensity::High => DiskDataRate::Rate500Kbps(1.0),
DiskDensity::Extended => DiskDataRate::Rate1000Kbps(1.0),
}
}
}
impl Display for DiskDataRate {
fn fmt(&self, fmt: &mut Formatter) -> fmt::Result {
match self {
DiskDataRate::RateNonstandard(rate) => write!(fmt, "*{}Kbps", rate / 1000),
DiskDataRate::Rate125Kbps(f) => write!(fmt, "125Kbps (x{:.2})", f),
DiskDataRate::Rate250Kbps(f) => write!(fmt, "250Kbps (x{:.2})", f),
DiskDataRate::Rate300Kbps(f) => write!(fmt, "300Kbps (x{:.2})", f),
DiskDataRate::Rate500Kbps(f) => write!(fmt, "500Kbps (x{:.2})", f),
DiskDataRate::Rate1000Kbps(f) => write!(fmt, "1000Kbps (x{:.2})", f),
}
}
}
#[derive(Copy, Clone, Debug, Default, PartialEq, Eq)]
pub enum DiskRpm {
#[default]
Rpm300,
Rpm360,
}
impl From<DiskRpm> for f64 {
fn from(rpm: DiskRpm) -> Self {
match rpm {
DiskRpm::Rpm300 => 300.0,
DiskRpm::Rpm360 => 360.0,
}
}
}
impl Display for DiskRpm {
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
match self {
DiskRpm::Rpm300 => write!(f, "300RPM"),
DiskRpm::Rpm360 => write!(f, "360RPM"),
}
}
}
impl DiskRpm {
pub fn from_index_time(time: f64) -> Option<DiskRpm> {
let rpm = 60.0 / time;
match rpm {
270.0..327.00 => Some(DiskRpm::Rpm300),
327.0..414.00 => Some(DiskRpm::Rpm360),
_ => None,
}
}
#[inline]
pub fn adjust_clock(&self, base_clock: f64) -> f64 {
if matches!(self, DiskRpm::Rpm360) && base_clock >= 1.5e-6 {
base_clock * (300.0 / 360.0)
}
else {
base_clock
}
}
}
pub use crate::{
chs::{DiskCh, DiskChs, DiskChsn},
diskimage::{DiskImage, DiskImageFileFormat, SectorMapEntry},
file_parsers::{format_from_ext, supported_extensions, ImageParser, ParserWriteCompatibility},
image_builder::ImageBuilder,
image_writer::ImageWriter,
standard_format::StandardFormat,
};
pub type DiskSectorMap = Vec<Vec<Vec<SectorMapEntry>>>;