a2kit/img/
nib.rs

1//! ## Support for NIB disk images
2//! 
3//! NIB tracks contain a filtered bitstream, i.e., leading bits that resolve to 0 are thrown out.
4//! We handle these tracks using the same track engine that handles WOZ tracks.  The trick is to
5//! set the sync-byte length to 8 bits.  This works so long as the NIB track has been properly aligned,
6//! and we are careful to start the bit pointer on a multiple of 8.
7//! 
8//! Nowadays use of NIB is discouraged because of its incomplete representation of the bit stream.
9
10use a2kit_macro::DiskStructError;
11use crate::img;
12use crate::img::{Track,Sector,SectorHood};
13use crate::img::tracks::{Method,FluxCells};
14use crate::img::tracks::gcr::TrackEngine;
15use crate::{STDRESULT,DYNERR};
16
17pub const TRACK_BYTE_CAPACITY_NIB: usize = 6656;
18pub const TRACK_BYTE_CAPACITY_NB2: usize = 6384;
19 
20pub fn file_extensions() -> Vec<String> {
21    vec!["nib".to_string(),"nb2".to_string()]
22}
23
24pub struct Nib {
25    kind: img::DiskKind,
26    fmt: Option<img::tracks::DiskFormat>,
27    tracks: usize,
28    trk_cap: usize,
29    data: Vec<u8>,
30    /// state: controller
31    engine: TrackEngine,
32    /// state: current track data and angle
33    cells: FluxCells,
34    /// state: current track
35    track_pos: usize,
36}
37
38impl Nib {
39    /// Create the image of a specific kind of disk (panics if unsupported disk kind).
40    /// The volume is used to format the address fields on the tracks.
41    pub fn create(vol: u8,kind: img::DiskKind) -> Result<Self,DYNERR> {
42        let fmt = match kind {
43            img::names::A2_DOS32_KIND => img::tracks::DiskFormat::apple_525_13(8),
44            img::names::A2_DOS33_KIND => img::tracks::DiskFormat::apple_525_16(8),
45            _ => {
46                log::error!("Nib can only accept 5.25 inch Apple formats");
47                return Err(Box::new(img::Error::ImageTypeMismatch));
48            }
49        };
50        let mut data: Vec<u8> = Vec::new();
51        let mut init_cells = None;
52        let mut engine = TrackEngine::create(Method::Fast,true);
53        for track in 0..35 {
54            let hood = SectorHood::a2_525(vol, track);
55            let zfmt = fmt.get_zone_fmt(0, 0)?;
56            let cells = engine.format_track(hood, TRACK_BYTE_CAPACITY_NIB,&zfmt)?;
57            let (mut buf,_) = cells.to_woz_buf(Some(TRACK_BYTE_CAPACITY_NIB),0xff);
58            if track==0 {
59                init_cells = Some(cells);
60            }
61            data.append(&mut buf);
62        }
63        Ok(Self {
64            kind,
65            fmt: Some(fmt),
66            tracks: 35,
67            trk_cap: TRACK_BYTE_CAPACITY_NIB,
68            data,
69            engine,
70            cells: init_cells.unwrap(),
71            track_pos: 0,
72        })
73    }
74    fn try_track(&self,trk: Track) -> Result<usize,DYNERR> {
75        match trk {
76            Track::Motor((m,h)) if m%4==0 && m < 140 && h==0 => Ok(m/4),
77            Track::CH((c,h)) if c < 35 && h==0 => Ok(c),
78            Track::Num(t) if t < 35 => Ok(t),
79            _ => {
80                log::error!("Nib image could not handle track key {}",trk);
81                Err(Box::new(img::Error::ImageTypeMismatch))
82            }
83        }
84    }
85    /// Get a reference to the track bits
86    fn get_trk_bits_ref(&self,trk: Track) -> Result<&[u8],DYNERR> {
87        let track = self.try_track(trk)?;
88        Ok(&self.data[track * self.trk_cap..(track+1) * self.trk_cap])
89    }
90    /// Get a mutable reference to the track bits
91    fn get_trk_bits_mut(&mut self,trk: Track) -> Result<&mut [u8],DYNERR> {
92        let track = self.try_track(trk)?;
93        Ok(&mut self.data[track * self.trk_cap..(track+1) * self.trk_cap])
94    }
95    /// Save changes to the track
96    fn write_back_track(&mut self) {
97        let track = self.track_pos;
98        let (src,_) = self.cells.to_woz_buf(Some(self.trk_cap), 0xff);
99        self.data[track * self.trk_cap..(track+1) * self.trk_cap].copy_from_slice(&src);
100    }
101    /// Goto track and extract FluxCells if necessary, returns [motor,head,width]
102    fn goto_track(&mut self,trk: Track) -> Result<[usize;3],DYNERR> {
103        let track = self.try_track(trk)?;
104        if self.track_pos != track {
105            log::debug!("goto track {} of {}",track,self.kind);
106            self.write_back_track();
107            self.track_pos = track;
108            let bit_count = self.trk_cap * 8;
109            self.cells = FluxCells::from_woz_bits(bit_count,self.get_trk_bits_ref(trk)?,0,false);
110        }
111        img::woz::get_motor_pos(trk, &self.kind)
112    }
113}
114
115impl img::DiskImage for Nib {
116    fn track_count(&self) -> usize {
117        self.tracks
118    }
119    fn end_track(&self) -> usize {
120        self.tracks
121    }
122    fn num_heads(&self) -> usize {
123        1
124    }
125    fn nominal_capacity(&self) -> Option<usize> {
126        match self.kind {
127            img::names::A2_DOS32_KIND => Some(self.tracks*13*256),
128            img::names::A2_DOS33_KIND => Some(self.tracks*16*256),
129            _ => None
130        }
131    }
132    fn actual_capacity(&mut self) -> Result<usize,DYNERR> {
133        match self.nominal_capacity() {
134            Some(ans) => Ok(ans),
135            None => Err(Box::new(img::Error::DiskKindMismatch))
136        }
137    }
138    fn what_am_i(&self) -> img::DiskImageType {
139        img::DiskImageType::NIB
140    }
141    fn file_extensions(&self) -> Vec<String> {
142        file_extensions()
143    }
144    fn kind(&self) -> img::DiskKind {
145        self.kind
146    }
147    fn change_kind(&mut self,kind: img::DiskKind) {
148        self.kind = kind;
149    }
150    fn read_block(&mut self,addr: crate::bios::Block) -> Result<Vec<u8>,DYNERR> {
151        crate::bios::blocks::apple::read_block(self, addr)
152    }
153    fn write_block(&mut self, addr: crate::bios::Block, dat: &[u8]) -> STDRESULT {
154        crate::bios::blocks::apple::write_block(self, addr, dat)
155    }
156    fn read_sector(&mut self,trk: Track,sec: Sector) -> Result<Vec<u8>,DYNERR> {
157        if self.fmt.is_none() {
158            return Err(Box::new(img::Error::UnknownDiskKind));
159        }
160        self.goto_track(trk)?;
161        let [motor,head,_] = img::woz::get_motor_pos(trk, &self.kind)?;
162        let fmt = self.fmt.as_ref().unwrap(); // guarded above 
163        let zfmt = fmt.get_zone_fmt(motor,head)?;
164        let hood = SectorHood::a2_525(254, u8::try_from((motor+1)/4)?);
165        let ans = self.engine.read_sector(&mut self.cells,&hood,&sec,zfmt)?;
166        Ok(ans)
167    }
168    fn write_sector(&mut self,trk: Track,sec: Sector,dat: &[u8]) -> Result<(),DYNERR> {
169        if self.fmt.is_none() {
170            return Err(Box::new(img::Error::UnknownDiskKind));
171        }
172        let [motor,head,_] = self.goto_track(trk)?;
173        let fmt = self.fmt.as_ref().unwrap(); // guarded above 
174        let zfmt = fmt.get_zone_fmt(motor,head)?.clone();
175        let hood = SectorHood::a2_525(254, u8::try_from((motor+1)/4)?);
176        self.engine.write_sector(&mut self.cells,dat,&hood,&sec,&zfmt)?;
177        Ok(())
178    }
179    fn from_bytes(buf: &[u8]) -> Result<Self,DiskStructError> where Self: Sized {
180        match buf.len() {
181            l if l==35*TRACK_BYTE_CAPACITY_NIB => {
182                let data = buf.to_vec();
183                let cells = FluxCells::from_woz_bits(TRACK_BYTE_CAPACITY_NIB*8, &data[0..TRACK_BYTE_CAPACITY_NIB],0,false);
184                let mut disk = Self {
185                    kind: img::names::A2_DOS33_KIND,
186                    fmt: Some(img::tracks::DiskFormat::apple_525_16(8)),
187                    tracks: 35,
188                    trk_cap: TRACK_BYTE_CAPACITY_NIB,
189                    data,
190                    engine: TrackEngine::create(Method::Fast, true),
191                    cells,
192                    track_pos: 0,
193                };
194                match disk.get_track_solution(Track::Num(0)) { Ok(_) => {
195                    log::debug!("setting disk kind to {}",disk.kind);
196                    return Ok(disk);
197                } _ => {
198                    log::debug!("Looks like NIB, but could not solve track 0");
199                    return Err(DiskStructError::UnexpectedValue);
200                }}
201            },
202            l if l==35*TRACK_BYTE_CAPACITY_NB2 => {
203                let data = buf.to_vec();
204                let cells = FluxCells::from_woz_bits(TRACK_BYTE_CAPACITY_NB2*8, &data[0..TRACK_BYTE_CAPACITY_NB2],0,false);
205                let mut disk = Self {
206                    kind: img::names::A2_DOS33_KIND,
207                    fmt: Some(img::tracks::DiskFormat::apple_525_16(8)),
208                    tracks: 35,
209                    trk_cap: TRACK_BYTE_CAPACITY_NB2,
210                    data,
211                    engine: TrackEngine::create(Method::Fast, true),
212                    cells,
213                    track_pos: usize::MAX
214                };
215                match disk.get_track_solution(Track::Num(0)) { Ok(_) => {
216                    log::debug!("setting disk kind to {}",disk.kind);
217                    return Ok(disk);
218                } _ => {
219                    log::debug!("Looks like NB2, but could not solve track 0");
220                    return Err(DiskStructError::UnexpectedValue);
221                }}
222            }
223            _ => {
224                log::debug!("Buffer size {} fails to match nib or nb2",buf.len());
225                Err(DiskStructError::UnexpectedSize)
226            }
227        }
228    }
229    fn to_bytes(&mut self) -> Vec<u8> {
230        self.write_back_track();
231        self.data.clone()
232    }
233    fn get_track_buf(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR> {
234        Ok(self.get_trk_bits_ref(trk)?.to_vec())
235    }
236    fn set_track_buf(&mut self,trk: Track,dat: &[u8]) -> STDRESULT {
237        let bits = self.get_trk_bits_mut(trk)?;
238        if bits.len()!=dat.len() {
239            log::error!("source track buffer is {} bytes, destination track buffer is {} bytes",dat.len(),bits.len());
240            return Err(Box::new(img::Error::ImageSizeMismatch));
241        }
242        bits.copy_from_slice(dat);
243        Ok(())
244    }
245    fn get_track_solution(&mut self,trk: Track) -> Result<img::TrackSolution,DYNERR> {
246        let [motor,head,_] = self.goto_track(trk)?;
247        // First try the given format if it exists
248        if let Some(fmt) = &self.fmt {
249            log::trace!("try current format");
250            let zfmt = fmt.get_zone_fmt(motor,head)?;
251            if let Ok((addr_map,size_map)) = self.engine.get_sector_map(&mut self.cells,zfmt) {
252                return Ok(zfmt.track_solution(addr_map,size_map,"VTSK",[255,255,255,255,0,0],None));
253            }
254        }
255        // If the given format fails try some standard ones
256        log::trace!("try DOS 3.2 format");
257        self.kind = img::names::A2_DOS32_KIND;
258        self.fmt = img::woz::kind_to_format(&self.kind);
259        let zfmt = img::tracks::get_zone_fmt(motor,head,&self.fmt)?;
260        if let Ok((addr_map,size_map)) = self.engine.get_sector_map(&mut self.cells,zfmt) {
261            if addr_map.len()==13 {
262                return Ok(zfmt.track_solution(addr_map,size_map,"VTSK",[255,255,255,255,0,0],None));
263            }
264        }
265        log::trace!("try DOS 3.3 format");
266        self.kind = img::names::A2_DOS33_KIND;
267        self.fmt = img::woz::kind_to_format(&self.kind);
268        let zfmt = img::tracks::get_zone_fmt(motor,head,&self.fmt)?;
269        if let Ok((addr_map,size_map)) = self.engine.get_sector_map(&mut self.cells,zfmt) {
270            if addr_map.len()==16 {
271                return Ok(zfmt.track_solution(addr_map,size_map,"VTSK",[255,255,255,255,0,0],None));
272            }
273        }
274        return Ok(img::TrackSolution::Unsolved);
275    }
276    fn export_geometry(&mut self,indent: Option<u16>) -> Result<String,DYNERR> {
277        let pkg = img::package_string(&self.kind());
278        let mut track_sols = Vec::new();
279        for track in 0..self.tracks {
280            let sol = self.get_track_solution(Track::Num(track))?;
281            let [c,h] = self.get_rz(Track::Num(track))?;
282            track_sols.push((c as f64,h,sol));
283        }
284        img::geometry_json(pkg,track_sols,35,1,1,indent)
285    }
286    fn export_format(&self,indent: Option<u16>) -> Result<String,DYNERR> {
287        match &self.fmt {
288            Some(f) => f.to_json(indent),
289            None => Err(Box::new(super::Error::UnknownFormat))
290        }
291    }
292    fn get_track_nibbles(&mut self,trk: Track) -> Result<Vec<u8>,DYNERR> {
293        let [motor,head,_] = self.goto_track(trk)?;
294        let zfmt = img::tracks::get_zone_fmt(motor, head, &self.fmt)?;
295        Ok(self.engine.to_nibbles(&mut self.cells, zfmt))
296    }
297    fn display_track(&self,bytes: &[u8]) -> String {
298        let trk = Track::Num(self.track_pos);
299        let [motor,head,_] = img::woz::get_motor_pos(trk, &self.kind).expect("could not get head position");
300        let zfmt = match img::tracks::get_zone_fmt(motor, head, &self.fmt) {
301            Ok(z) => Some(z),
302            _ => None
303        };
304        super::woz::track_string_for_display(0, &bytes, zfmt)
305    }
306}