Skip to main content

a2kit/img/
tracks.rs

1//! # Track Engines and Formats
2//!
3//! This module provides tools for working with tracks at the bitstream or flux-stream level.
4//! The `DiskFormat` struct provides everything needed to create, read, and write disk tracks.
5//! It breaks up into `ZoneFormat` structs that are often passed into track handling functions.
6//! Functions are provided to create standard formats.
7//! A format can also be created from a JSON string provided externally.
8//! 
9//! A key concept is that the format contains expressions describing a transformation
10//! from standard sector addresses to proprietary ones.  This way a sector can always be
11//! found using a standard address; the entity doing the seeking merely has to apply
12//! the given transformation.
13//! 
14//! Standard sector addresses are not necessarily geometrical.
15//! If there is an interleave on the standard track associated with this disk kind,
16//! then standard sector 1 is not the geometrical neighbor or standard sector 2.
17
18use crate::img;
19use crate::DYNERR;
20use crate::STDRESULT;
21use std::fmt;
22use std::str::FromStr;
23use std::collections::HashMap;
24use math_parse::MathParse;
25use bit_vec::BitVec;
26
27pub mod gcr;
28mod formats;
29mod parse_user_fmt;
30
31fn eval_u8(expr: &str,ctx: &std::collections::HashMap<String,String>) -> Result<u8,DYNERR> {
32    match MathParse::parse(expr) {
33        Ok(parsed) => match parsed.solve_int(Some(ctx)) {
34            Ok(ans) => match u8::try_from(ans) {
35                Ok(ans) => Ok(ans),
36                Err(_) => {
37                    log::error!("{} evaluated to {} which is not a u8",expr,ans);
38                    Err(Box::new(img::Error::MetadataMismatch))
39                }
40            }
41            Err(e) => {
42                log::error!("problem solving {}: {}",expr,e);
43                if expr.contains("/") && !expr.contains("//") {
44                    log::warn!("floating point division detected in user expression, use `//` for integer division")
45                }
46                log::debug!("variables: {:?}",ctx);
47                Err(Box::new(img::Error::MetadataMismatch))
48            }
49        },
50        Err(e) => {
51            log::error!("problem parsing {}: {}",expr,e);
52            Err(Box::new(img::Error::MetadataMismatch))
53        }
54    }
55}
56
57/// For flux tracks we want to estimate the typical bit-cell duration so we can
58/// calibrate the disk speed if necessary.  This assumes the answer is < 64 ticks.
59/// The input buffer is a flux buffer in WOZ-2 format.
60/// Might return None if the track is something pathological.
61fn estimate_bit_cell_duration(flux_timings: &[u8]) -> Option<usize> {
62    // This is the fraction of nearest-neighbor pulses that are separated by a bit cell.
63    // The algorithm is robust against an understimate, but not against an overestimate.
64    let min_frac = [1,5];
65    let mut histogram: Vec<usize> = vec![0;64];
66    let mut last = 0;
67    let mut total_counts = 0;
68    for dt in flux_timings {
69        if *dt < 64 && last != 255 {
70            histogram[*dt as usize] += 1;
71            total_counts += 1;
72        }
73        last = *dt;
74    }
75    let mut running_counts = 0;
76    for i in 0..64 {
77        running_counts += histogram[i];
78        if running_counts*min_frac[1] > total_counts*min_frac[0] {
79            return Some(i);
80        }
81    }
82    return None;
83}
84
85#[derive(Clone,PartialEq)]
86pub enum Method {
87    /// select based on track
88    Auto,
89    /// direct manipulation, good for textbook data streams
90    Fast,
91    /// emulate, but suppress effects that might confuse analysis
92    Analyze,
93    /// emulate the real system as near as possible
94    Emulate,
95}
96
97impl FromStr for Method {
98    type Err = super::Error;
99    fn from_str(s: &str) -> Result<Self,Self::Err> {
100        match s {
101            "auto" => Ok(Method::Auto),
102            "analyze" => Ok(Method::Analyze),
103            "fast" => Ok(Method::Fast),
104            "emulate" => Ok(Method::Emulate),
105            _ => Err(super::Error::MetadataMismatch)
106        }
107    }
108}
109
110impl fmt::Display for Method {
111    fn fmt(&self,f: &mut fmt::Formatter<'_>) -> fmt::Result {
112        match self {
113            Self::Auto => write!(f,"auto"),
114            Self::Analyze => write!(f,"analyze"),
115            Self::Emulate => write!(f,"emulate"),
116            Self::Fast => write!(f,"fast"),
117        }
118    }
119}
120
121/// While this object is in the form of flux cells, it can actually be used for
122/// any form of track data: flux streams, bit streams, or nibble streams.
123/// Each bit in the stream represents a flux-cell where there may be a flux transition.
124/// If `fshift < bshift`, we have a flux stream.
125/// If `fshift == bshift`, we have a bit stream.
126/// If uniform-length nibbles are tightly packed, we have a nibble stream.
127pub struct FluxCells {
128    /// each bit represents a window of time, bit value indicates whether there is a transition
129    stream: BitVec,
130    /// current location in the flux stream in units of ticks
131    ptr: usize,
132    /// emulated time in ticks, origin of time is up to caller
133    time: u64,
134    /// one revolution in units of ticks
135    revolution: usize,
136    /// defines a tick, fixing the basis of time in picoseconds
137    tick_ps: usize,
138    /// `1 << fshift` is length of a flux cell in ticks
139    fshift: usize,
140    /// `ptr & fmask == 0` indicates start of flux cell
141    fmask: usize,
142    /// `1 << bshift` is length of a bit cell in ticks
143    bshift: usize,
144    /// `ptr & bmask == 0` indicates start of bit cell
145    bmask: usize,
146    /// ratio that was used to recalibrate timing
147    recal: [usize;2]
148}
149
150impl FluxCells {
151    /// Create a NIB or WOZ bitstream with 4 or 2 microsecond bit cells
152    fn new_woz_bits(ptr: usize,stream: BitVec,time: u64,double_speed: bool) -> Self {
153        let shft = double_speed as usize;
154        let revolution = stream.len() << 5;
155        Self {
156            stream,
157            ptr,
158            time,
159            revolution,
160            tick_ps: 125000 >> shft,
161            fshift: 5,
162            fmask: (1 << 5) - 1,
163            bshift: 5,
164            bmask: (1 << 5) - 1,
165            recal: [1,1]
166        }
167    }
168    /// Create a WOZ fluxstream with 500 or 250 nanosecond flux cells
169    fn new_woz_flux(ptr: usize,stream: BitVec,time: u64,double_speed: bool,recal: [usize;2]) -> Self {
170        let shft = double_speed as usize;
171        let revolution = stream.len() << 2;
172        Self {
173            stream,
174            ptr,
175            time,
176            revolution,
177            tick_ps: 125000 >> shft,
178            fshift: 2,
179            fmask: (1 << 2) - 1,
180            bshift: 5,
181            bmask: (1 << 5) - 1,
182            recal
183        }
184    }
185    /// Create cells from a track buffer in the form of a nibble stream or bit stream, assuming 4 microsecond bit cells
186    pub fn from_woz_bits(bit_count: usize,buf: &[u8],time: u64,double_speed: bool) -> Self {
187        let mut stream = BitVec::from_bytes(buf);
188        stream.truncate(bit_count);
189        Self::new_woz_bits(0,stream,time,double_speed)
190    }
191    /// Create cells from a WOZ flux track buffer, the flux cells will
192    /// be set to 500/250 ns.  Any padding in `buf` is ignored.
193    pub fn from_woz_flux(byte_count: usize,buf: &[u8],time: u64,double_speed: bool) -> Self {
194        let mut recal = [1,1];
195        if let Some(median) = estimate_bit_cell_duration(&buf[0..byte_count]) {
196            log::trace!("bit cell is {}",median);
197            recal[0] = match double_speed { true => 0x10, false => 0x20};
198            recal[1] = median;
199        }
200        // The flux cell is chosen to align to the state machine cycle, 500/250 ns for 5.25/3.5 inch disks.
201        // The WOZ ticks we are reading are always 125 ns regardless of disk kind.
202        let ticks_per_cell = 4 >> double_speed as usize;
203        let mut stream = BitVec::new();
204        let mut write = |carryover0: &mut usize, carryover1: &mut usize| {
205            let ticks = *carryover0 + *carryover1;
206            let cells = ticks / ticks_per_cell;
207            for j in 0..cells {
208                stream.push(j==0 && *carryover1 > 0 || j==cells-1);
209            }
210            *carryover0 = (cells>0) as usize * (ticks % ticks_per_cell);
211            *carryover1 = (cells==0) as usize * (ticks % ticks_per_cell);
212        };
213        let mut carryover0 = 0;
214        let mut carryover1 = 0;
215        for segment in &buf[0..byte_count] {
216            carryover0 += *segment as usize * recal[0] / recal[1];
217            if *segment!=255 {
218                write(&mut carryover0,&mut carryover1);
219            }
220        }
221        write(&mut carryover0,&mut carryover1);
222        Self::new_woz_flux(0,stream,time,double_speed,recal)
223    }
224    /// Change the resolution of the cells. Mainly useful for converting between bitstream tracks and flux tracks.
225    /// N.b. if resolution is being reduced information will be lost, in general.
226    pub fn change_resolution(&mut self,flux_shift: usize) {
227        if flux_shift == self.fshift {
228            return;
229        } else if flux_shift < self.fshift {
230            // resolution is higher
231            let factor = 1 << self.fshift >> flux_shift;
232            let mut new_stream = BitVec::new();
233            for bit in &self.stream {
234                new_stream.push(bit);
235                new_stream.append(&mut BitVec::from_elem(factor-1,false));
236            }
237            self.fshift = flux_shift;
238            self.fmask = (1 << flux_shift) - 1;
239            self.stream = new_stream;
240        } else {
241            // resolution is lower
242            let factor = 1 << flux_shift >> self.fshift;
243            let new_len = self.stream.len() << self.fshift >> flux_shift;
244            let mut new_stream = BitVec::new();
245            for i in 0..new_len {
246                let mut val = false;
247                for j in 0..factor {
248                    val |= self.stream[i*factor+j];
249                }
250                new_stream.push(val);
251            }
252            self.fshift = flux_shift;
253            self.fmask = (1 << flux_shift) - 1;
254            self.stream = new_stream;
255        }
256    }
257    /// Convert the cells to the native WOZ or NIB track buffer, works
258    /// for any kind of cell.  If `padded_len==None` the result is padded
259    /// to the nearest 512 byte boundary.  If `padded_len==Some` and the data fits within
260    /// the prescribed length, it is used, otherwise panic.
261    /// Returns (buf,count), where count is either the
262    /// bit count for bit streams, or byte count for flux streams.
263    pub fn to_woz_buf(&self,padded_len: Option<usize>,padded_val: u8) -> (Vec<u8>,usize) {
264        let (mut buf,count) = if self.fshift==self.bshift {
265            (self.stream.to_bytes(),self.stream.len())
266        } else {
267            let mut zeroes = 0;
268            let ticks_per_cell = ((1 << self.fshift) * self.tick_ps / 125000) as u8;
269            let mut end = usize::MAX;
270            let mut buf = Vec::new();
271            // figure out where the last transition is and make that the end,
272            // otherwise we could have a broken encoding
273            for (i,cell) in self.stream.iter().rev().enumerate() {
274                if cell {
275                    end = self.stream.len() - i;
276                    break;
277                }
278            }
279            if end==usize::MAX {
280                log::info!("no flux transitions on track");
281                end = 1;
282            }
283            let mut iter_clos = |cell| {
284                if cell {
285                    // The following line presumes the tick count is inclusive of whatever time
286                    // the transition takes, or else the transition is infinitessimal in duration. 
287                    buf.push(zeroes + ticks_per_cell);
288                    zeroes = 0;
289                } else {
290                    zeroes += ticks_per_cell;
291                }
292                if zeroes >= 0xff {
293                    buf.push(0xff);
294                    zeroes = zeroes % 0xff;
295                }
296            };
297            for i in end..self.stream.len() {
298                iter_clos(self.stream[i]);
299            }
300            for i in 0..end {
301                iter_clos(self.stream[i]);
302            }
303            let count = buf.len();
304            (buf,count)
305        };
306        let padding = match (buf.len(),padded_len) {
307            (l,Some(tot)) if l<=tot => tot-l,
308            (l,Some(tot)) => panic!("buffer too small {}/{}",l,tot),
309            (l,_) if l==0 => 512,
310            (l,_) => ((l-1)/512)*512 + 512 - l
311        };
312        buf.append(&mut vec![padded_val;padding]);
313        (buf,count)
314    }
315    /// Synchronize these cells to another set of cells, used when switching tracks.
316    /// This will impose alignment of the time-pointer to a flux cell boundary.
317    pub fn sync_to_other_track(&mut self,other: &FluxCells) {
318        self.ptr = (other.ptr * self.revolution / other.revolution) >> self.fshift << self.fshift;
319    }
320    /// How many cells are on this track
321    pub fn count(&self) -> usize {
322        self.stream.len()
323    }
324    pub fn set_ptr(&mut self,ticks: usize) {
325        self.ptr = ticks;
326    }
327    /// advance on the track by `ticks` and update the elapsed time
328    pub fn fwd(&mut self,ticks: usize) {
329        self.ptr = (self.ptr + ticks) % self.revolution;
330        self.time += ticks as u64;
331    }
332    /// Go back on the track by `ticks`, this will also reverse the elapsed time (pegs to 0).
333    /// Avoid using, saving and restoring state is usually preferable.
334    pub fn rev(&mut self,ticks: usize) {
335        self.ptr = (self.ptr + self.revolution - ticks) % self.revolution;
336        self.time -= match ticks as u64 > self.time {
337            true => self.time,
338            false => ticks as u64
339        };
340    }
341    /// ticks since the reference tick, if ref_tick is in the future return 0
342    pub fn ticks_since(&self,ref_tick: u64) -> u64 {
343        match ref_tick > self.time {
344            true => 0,
345            false => self.time - ref_tick
346        }
347    }
348    /// picoseconds since the reference tick, if ref_tick is in the future return 0
349    pub fn ps_since(&self,ref_tick: u64) -> u64 {
350        match ref_tick > self.time {
351            true => 0,
352            false => self.tick_ps as u64 * (self.time - ref_tick)
353        }
354        
355    }
356    /// If there is timing information, return the density of the original data relative
357    /// to the expected density, otherwise return None.
358    pub fn density(&self) -> Option<f64> {
359        if self.recal[1] > 0 && self.bshift != self.fshift {
360            Some(self.recal[0] as f64 / self.recal[1] as f64)
361        } else {
362            None
363        }
364    }
365    /// emit a pulse from the current bit cell and advance
366    pub fn read_bit(&mut self) -> bool {
367        let cells_per_bit = 1 << self.bshift >> self.fshift;
368        let mut ans = false;
369        for _ in 0..cells_per_bit {
370            ans |= self.stream[self.ptr >> self.fshift];
371            self.fwd(1 << self.fshift);
372        }
373        ans
374    }
375    /// write a pulse to the current bit cell and advance
376    pub fn write_bit(&mut self,pulse: bool) {
377        let cells_per_bit = 1 << self.bshift >> self.fshift;
378        for _ in 0..cells_per_bit {
379            self.stream.set(self.ptr >> self.fshift,pulse && (self.ptr & self.bmask==0));
380            self.fwd(1 << self.fshift);
381        }
382    }
383}
384
385/// bit pattern that marks off a sector address or data run,
386/// for FM/MFM the pattern shall include clock pulses
387#[derive(Clone)]
388struct SectorMarker {
389    key: Vec<u8>,
390    mask: Vec<u8>,
391}
392
393/// Format of a contiguous set of tracks.
394#[derive(Clone)]
395pub struct ZoneFormat {
396    flux_code: img::FluxCode,
397    addr_nibs: img::FieldCode,
398    data_nibs: img::FieldCode,
399    speed_kbps: usize,
400    motor_start: usize,
401    motor_end: usize,
402    motor_step: usize,
403    heads: Vec<usize>,
404    /// Ordered expressions used to calculate sector address bytes for use during formatting, including checksum.
405    /// The expressions give the decoded bytes, in order, in terms standard variables (vol,cyl,head,sec,aux).
406    /// For complex CRC bytes, some identifier will have to be used in place of an expression.
407    addr_fmt_expr: Vec<String>,
408    /// Ordered expressions used to calculate sector address bytes for use during seeking, including checksum.
409    /// In addition to (vol,cyl,head,sec,aux), variables may include (a0,a1,a2,...).  The latter refer to the
410    /// actual address values.  These will generally be used in the checksum, and can
411    /// also be used to effectively mask out bits you don't need to match.
412    addr_seek_expr: Vec<String>,
413    /// In most cases this is simply `["dat"]`, which means sector data and checksum.
414    /// We do not try to describe data checksums here as they can be very complex.
415    /// Any other expressions are evaluated as byte values and packed into the data field in the order given.
416    data_expr: Vec<String>,
417    /// fixed markers used to identify address start, address stop, data start, data stop
418    markers: [SectorMarker; 4],
419    /// gaps at start of track, end of sector, end of data (often for syncing)
420    gaps: [BitVec; 3],
421    /// When reading replace `swap_nibs[i][0]` with `swap_nibs[i][1]`, when writing do the opposite.
422    swap_nibs: Vec<[u8;2]>,
423    /// Capacity of each sector, in some cases the possible values are tightly constrained
424    capacity: Vec<usize>
425}
426
427/// Format of a disk broken up into zones.
428#[derive(Clone)]
429pub struct DiskFormat {
430    zones: Vec<ZoneFormat>
431}
432
433impl fmt::Display for img::Track {
434    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
435        match self {
436            Self::CH((c, h)) => write!(f, "cyl {} head {}", c, h),
437            Self::Motor((m, h)) => write!(f, "motor-pos {} head {}", m, h),
438            Self::Num(t) => write!(f, "track {}", t),
439        }
440    }
441}
442
443impl PartialOrd for img::Track {
444    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
445        match (self,other) {
446            (Self::CH(x),Self::CH(y)) => x.partial_cmp(y),
447            (Self::Motor(x),Self::Motor(y)) => x.partial_cmp(y),
448            (Self::Num(x),Self::Num(y)) => x.partial_cmp(y),
449            _ => None
450        }
451    }
452}
453
454impl img::Track {
455    /// jump in units of normal cylinder separation, if this is a `Track` discriminant an error is returned
456    pub fn jump(&mut self, cyls: isize, new_head: Option<usize>, steps_per_cyl: usize) -> STDRESULT {
457        match self {
458            Self::CH((c,h)) => {
459                *c = usize::try_from(*c as isize + cyls)?;
460                if let Some(head) = new_head {
461                    *h = head;
462                }
463                Ok(())
464            },
465            Self::Motor((m,h)) => {
466                *m = usize::try_from(*m as isize + cyls * steps_per_cyl as isize)?;
467                if let Some(head) = new_head {
468                    *h = head;
469                }
470                Ok(())
471            },
472            _ => Err(Box::new(crate::commands::CommandError::InvalidCommand))
473        }
474    }
475}
476
477impl img::Sector {
478    /// consume this sector and create an explicit one
479    pub fn to_explicit(&mut self,hex_str: &str) -> STDRESULT {
480        let idx = match self {
481            Self::Num(n) => n,
482            Self::Addr((n,_)) => n
483        };
484        if hex_str.len() < 6 {
485            log::error!("sector address is too short");
486            return Err(Box::new(img::Error::SectorAccess));
487        }
488        *self = Self::Addr((*idx,hex::decode(hex_str)?));
489        Ok(())
490    }
491}
492
493impl fmt::Display for img::Sector {
494    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
495        match self {
496            Self::Num(n) => write!(f, "{}",n),
497            Self::Addr((n,a)) => write!(f, "{}:{:?}",n,a)
498        }
499    }
500}
501
502impl fmt::Display for img::SectorHood {
503    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
504        write!(f, "{},{},{},{}",self.vol,self.cyl,self.head,self.aux)
505    }
506}
507
508impl img::SectorHood {
509    fn get_fmt_vars(&self,sec: u8) -> Result<HashMap<String,String>,DYNERR> {
510        let mut ctx = HashMap::new();
511        ctx.insert("vol".to_string(),u8::to_string(&self.vol));
512        ctx.insert("cyl".to_string(),u8::to_string(&self.cyl));
513        ctx.insert("head".to_string(),u8::to_string(&self.head));
514        ctx.insert("sec".to_string(),u8::to_string(&sec));
515        ctx.insert("aux".to_string(),u8::to_string(&self.aux));
516        Ok(ctx)
517    }
518    fn get_seek_vars(&self,sec: u8,actual: &[u8]) -> Result<HashMap<String,String>,DYNERR> {
519        let mut ctx = HashMap::new();
520        ctx.insert("vol".to_string(),u8::to_string(&self.vol));
521        ctx.insert("cyl".to_string(),u8::to_string(&self.cyl));
522        ctx.insert("head".to_string(),u8::to_string(&self.head));
523        ctx.insert("sec".to_string(),u8::to_string(&sec));
524        ctx.insert("aux".to_string(),u8::to_string(&self.aux));
525        for i in 0..actual.len() {
526            ctx.insert(["a",&usize::to_string(&i)].concat(),u8::to_string(&actual[i]));
527        }
528        Ok(ctx)
529    }
530    pub fn head(&self) -> u8 {
531        self.head
532    }
533    pub fn a2_525(vol: u8,trk: u8) -> Self {
534        Self {
535            vol,
536            cyl: trk,
537            head: 0,
538            aux: 0
539        }
540    }
541    pub fn a2_35(cyl: u8,head: u8) -> Self {
542        Self {
543            vol: 0,
544            cyl,
545            head,
546            aux: 0
547        }
548    }
549    pub fn c64(cyl: u8,aux: u8) -> Self {
550        Self {
551            vol: 0,
552            cyl,
553            head: 0,
554            aux
555        }
556    }
557    pub fn ibm(cyl: u8,head: u8,aux: u8) -> Self {
558        Self {
559            vol: 0,
560            cyl,
561            head,
562            aux
563        }
564    }
565}
566
567impl ZoneFormat {
568    pub fn check_flux_code(&self,flux_code: img::FluxCode) -> STDRESULT {
569        if flux_code == self.flux_code {
570            Ok(())
571        } else {
572            Err(Box::new(super::Error::ImageTypeMismatch))
573        }
574    }
575    /// returns (key,mask)
576    fn get_marker(&self, which: usize) -> (&[u8],&[u8]) {
577        (
578            &self.markers[which].key,
579            &self.markers[which].mask
580        )
581    }
582    fn get_gap_bits(&self, which: usize) -> &BitVec {
583        &self.gaps[which]
584    }
585    /// Get a sector address field appropriate for use in formatting.
586    /// The inputs are transformed by expressions stored with the format.
587    /// The formatter may rearrange the address, but it does *not* encode the address.
588    /// The formatter can also compute simple checksums.
589    fn get_addr_for_formatting(&self,hood: &img::SectorHood,sec: &img::Sector) -> Result<Vec<u8>,DYNERR> {
590        match sec {
591            img::Sector::Addr((_,addr)) => {
592                let mut ans = addr.clone();
593                match (addr.len(),self.addr_nibs) {
594                    (3,img::FieldCode::WOZ((4,4))) => ans.push(addr[0] ^ addr[1] ^ addr[2]),
595                    (4,img::FieldCode::WOZ((6,2))) => ans.push((addr[0] ^ addr[1] ^ addr[2] ^ addr[3]) & 63),
596                    _ => {}
597                }
598                Ok(ans)
599            },
600            img::Sector::Num(sec) => {
601                let mut ans = Vec::new();
602                let ctx = hood.get_fmt_vars((*sec).try_into()?)?;
603                for expr in &self.addr_fmt_expr {
604                    ans.push(eval_u8(expr,&ctx)?);
605                }
606                Ok(ans)
607            }
608        }
609    }
610    /// Get a sector address field to be matched against an actual address field during seeking.
611    /// The inputs are transformed by expressions stored with the format. These transformations
612    /// can (and usually do) involve `actual`, which should be the decoded address field actually found.
613    /// In particular, checksums are normally matched against the checksum of the actual bytes.
614    fn get_addr_for_seeking(&self,hood: &img::SectorHood,sec: &img::Sector,actual: &[u8]) -> Result<Vec<u8>,DYNERR> {
615        match sec {
616            img::Sector::Addr((_,addr)) => {
617                let mut ans = addr.clone();
618                match (addr.len(),self.addr_nibs) {
619                    (3,img::FieldCode::WOZ((4,4))) => ans.push(addr[0] ^ addr[1] ^ addr[2]),
620                    (4,img::FieldCode::WOZ((6,2))) => ans.push((addr[0] ^ addr[1] ^ addr[2] ^ addr[3]) & 63),
621                    _ => {}
622                }
623                Ok(ans)
624            },
625            img::Sector::Num(sec) => {
626                let mut ans = Vec::new();
627                let ctx = hood.get_seek_vars((*sec).try_into()?,actual)?;
628                for expr in &self.addr_seek_expr {
629                    ans.push(eval_u8(expr,&ctx)?);
630                }
631                Ok(ans)
632            }
633        }
634    }
635    /// Returns `(actual ^ pattern)` for each address byte.  If all bytes are 0 this is a match.
636    /// This will transform (but not encode) the arguments according the expressions stored with this format before comparing.
637    fn diff_addr(&self, hood: &img::SectorHood, sec: &img::Sector, actual: &[u8]) -> Result<Vec<u8>,DYNERR> {
638        let mut ans = Vec::new();
639        let pattern = self.get_addr_for_seeking(hood,sec,actual)?;
640        if pattern.len() != actual.len() {
641            log::error!("lengths did not match during address comparison");
642            return Err(Box::new(super::Error::SectorAccess));
643        }
644        for i in 0..pattern.len() {
645            ans.push(actual[i] ^ pattern[i]);
646        }
647        Ok(ans)
648    }
649    /// Return any information (not markers) that should precede the sector data (often none).
650    fn get_data_header(&self,hood: &img::SectorHood,sec: &img::Sector) -> Result<Vec<u8>,DYNERR> {
651        let idx = match sec {
652            img::Sector::Addr((n,_)) => *n,
653            img::Sector::Num(n) => *n
654        };
655        let mut ans = Vec::new();
656        let ctx = hood.get_fmt_vars((idx).try_into()?)?;
657        // work out every byte until we encounter "dat"
658        for expr in &self.data_expr {
659            if expr == "dat" {
660                return Ok(ans);
661            } else {
662                ans.push(eval_u8(expr,&ctx)?);
663            }
664        }
665        Ok(ans)
666    }
667    pub fn addr_nibs(&self) -> img::FieldCode {
668        self.addr_nibs
669    }
670    fn data_nibs(&self) -> img::FieldCode {
671        self.data_nibs
672    }
673    /// `sec` is sector id before any format transformation happens, and is used as the index
674    /// into the capacity vector.  Wrap around is used to always give an answer.  Will panic if
675    /// the capacity vector is empty.
676    fn capacity(&self,sec: &img::Sector) -> usize {
677        let idx = match sec {
678            img::Sector::Addr((n,_)) => *n,
679            img::Sector::Num(n) => *n
680        };
681        self.capacity[idx % self.capacity.len()]
682    }
683    pub fn sector_count(&self) -> usize {
684        self.capacity.len()
685    }
686    pub fn track_solution(&self,addr_map: Vec<[u8;6]>,size_map: Vec<usize>,addr_type: &str,addr_mask: [u8;6],density: Option<f64>) -> img::TrackSolution {
687        img::TrackSolution::Solved(img::SolvedTrack {
688            speed_kbps: self.speed_kbps,
689            density,
690            flux_code: self.flux_code,
691            addr_code: self.addr_nibs,
692            data_code: self.data_nibs,
693            addr_type: addr_type.to_string(),
694            addr_mask,
695            addr_map,
696            size_map
697        })
698    }
699    /// see if `win` matches marker `which` at any stage and return the mnemonic.
700    /// update the marker information if matching the final byte.
701    fn chk_marker(&self, i: usize, win: &[u8;5], which: usize, mnemonic: &[char], fallback: char, last_marker: &mut usize, last_marker_end: &mut usize) -> char {
702        let count = usize::min(3,self.markers[which].key.len());
703        for stage in 0..count {
704            let mut matching = true;
705            for i in 0..count {
706                let y = self.markers[which].key[i];
707                let mask = self.markers[which].mask[i];
708                matching &= win[2-stage+i] & mask == y & mask;
709            }
710            if matching {
711                if stage+1==count {
712                    *last_marker += 1;
713                    *last_marker_end = i + 1;
714                    if *last_marker > 3 {
715                        *last_marker = 0;
716                    }
717                }
718                return mnemonic[stage%mnemonic.len()];
719            }
720        }
721        fallback
722    }
723    /// Analyzes a neighborhood of the WOZ-like nibble stream given the most recent marker that has been seen,
724    /// and produce a character that can be used to guide the eye to interesting regions in the stream.
725    /// The `last_marker` 0 means starting or data epilog found, and so on in sequence
726    pub fn woz_mnemonic(&self,buf: &[u8],i: usize,last_marker: &mut usize,last_marker_end: &mut usize) -> char {
727        let hexdigit = |x| match x {
728            x if x < 10 => char::from_u32(x as u32 + 48).unwrap_or('^'),
729            x if x < 16 => char::from_u32(x as u32 + 87).unwrap_or('^'),
730            _ => '^'
731        };
732        let addr_nib_count = match self.addr_nibs() {
733            img::FieldCode::WOZ((4,4)) => 2*self.addr_fmt_expr.len(),
734            _ => self.addr_fmt_expr.len()
735        };
736        let data_nib_count = match (self.capacity(&img::Sector::Num(0)),self.data_nibs()) {
737            (256,img::FieldCode::WOZ((4,4))) => 512,
738            (256,img::FieldCode::WOZ((5,3))) => 411,
739            (256,img::FieldCode::WOZ((6,2))) => 343,
740            (524,img::FieldCode::WOZ((6,2))) => 703,
741            _ => 343
742        };
743        let mut win = [0;5];
744        for rel in 0..5 {
745            let abs = i as isize - 2 + rel;
746            if abs >= 0 && abs < buf.len() as isize {
747                win[rel as usize] = buf[abs as usize];
748            }
749        }
750        let invalid = gcr::decode(buf[i] as usize + 0xaa00, &self.data_nibs).is_err();
751        let mut fallback = match (invalid,buf[i]) {
752            (true,0xd5) => 'R',
753            (true,0xaa) => 'R',
754            (true,_) => '?',
755            _ => '.'
756        };
757        // if we have been looking for a data prolog too long give up and look for next address prolog
758        if *last_marker == 2 && i > *last_marker_end + 40 {
759            *last_marker = 0;
760        }
761        if *last_marker == 0 {
762            // DA gap
763            if win[2] == 0xff {
764                fallback = '>';
765            }
766            self.chk_marker(i,&win,0,&['(','A',':'],fallback,last_marker,last_marker_end)
767        } else if *last_marker==1 && i < *last_marker_end + addr_nib_count {
768            // address field
769            match self.addr_nibs() {
770                img::FieldCode::WOZ((4,4)) => {
771                    if (i-*last_marker_end)%2 == 0 {
772                        let val = buf[i] as usize * 256 + win[3] as usize;
773                        hexdigit(gcr::decode(val,&self.addr_nibs).unwrap_or(0) >> 4)
774                    } else {
775                        let val = win[1] as usize * 256 + buf[i] as usize;
776                        hexdigit(gcr::decode(val,&self.addr_nibs).unwrap_or(0) & 0x0f)
777                    }
778                },
779                _ => hexdigit(gcr::decode(buf[i] as usize,&self.addr_nibs).unwrap_or(0))
780            }
781        } else if *last_marker==1 {
782            // address epilog
783            self.chk_marker(i,&win,1,&[':','A',')'],fallback,last_marker,last_marker_end)
784        } else if *last_marker==2 {
785            // AD gap
786            if win[2] == 0xff {
787                fallback = '>';
788            }
789            self.chk_marker(i,&win,2,&['(','D',':'],fallback,last_marker,last_marker_end)
790        } else if *last_marker==3 && i < *last_marker_end + data_nib_count {
791            // data field
792            fallback
793        } else if *last_marker==3 {
794            // data epilog
795            self.chk_marker(i,&win,3,&[':','D',')'],fallback,last_marker,last_marker_end)
796        } else {
797            fallback
798        }
799    }
800}
801
802/// short cut to get a ZoneFormat from a maybe DiskFormat
803pub fn get_zone_fmt<'a>(motor: usize,head: usize,fmt: &'a Option<DiskFormat>) -> Result<&'a ZoneFormat,DYNERR> {
804	match fmt {
805		Some(f) => Ok(f.get_zone_fmt(motor,head)?),
806		None => Err(Box::new(img::Error::UnknownFormat))
807	}
808}
809
810impl<'a> DiskFormat {
811    pub fn get_zone_fmt(&'a self,motor: usize,head: usize) -> Result<&'a ZoneFormat,DYNERR> {
812        for zone in &self.zones {
813            if motor >= zone.motor_start && motor < zone.motor_end && zone.heads.contains(&head) {
814                return Ok(zone)
815            }
816        }
817        log::info!("zone at motor pos {} not found",motor);
818        return Err(Box::new(super::Error::UnexpectedZone))
819    }
820    /// concatenate all the (motor,head) tuples for all the zones
821    pub fn get_motor_and_head(&self) -> Vec<(usize,usize)> {
822        let mut ans = Vec::new();
823        for zone in &self.zones {
824            for m in (zone.motor_start..zone.motor_end).step_by(zone.motor_step) {
825                for h in &zone.heads {
826                    ans.push((m,*h));
827                }
828            }
829        }
830        ans
831    }
832}