|  | @@ -5,24 +5,113 @@
 | 
	
		
			
				|  |  |  # This is free and unencumbered software released into the public domain.
 | 
	
		
			
				|  |  |  # See the file COPYING for more details, or visit <http://unlicense.org>.
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -import math, struct
 | 
	
		
			
				|  |  | +import binascii, math, struct
 | 
	
		
			
				|  |  |  import itertools as it
 | 
	
		
			
				|  |  | +from bitarray import bitarray
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |  from greaseweazle import error
 | 
	
		
			
				|  |  |  from greaseweazle.codec.ibm import mfm
 | 
	
		
			
				|  |  | +from greaseweazle.track import MasterTrack, RawTrack
 | 
	
		
			
				|  |  |  from .image import Image
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +class EDSKTrack:
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gap_presync = 12
 | 
	
		
			
				|  |  | +    gap_4a = 80 # Post-Index
 | 
	
		
			
				|  |  | +    gap_1  = 50 # Post-IAM
 | 
	
		
			
				|  |  | +    gap_2  = 22 # Post-IDAM
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    gapbyte = 0x4e
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    def __init__(self):
 | 
	
		
			
				|  |  | +        self.time_per_rev = 0.2
 | 
	
		
			
				|  |  | +        self.clock = 2e-6
 | 
	
		
			
				|  |  | +        self.bits, self.weak = [], []
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def raw_track(self):
 | 
	
		
			
				|  |  | +        track = MasterTrack(
 | 
	
		
			
				|  |  | +            bits = self.bits,
 | 
	
		
			
				|  |  | +            time_per_rev = self.time_per_rev,
 | 
	
		
			
				|  |  | +            weak = self.weak)
 | 
	
		
			
				|  |  | +        track.verify = self
 | 
	
		
			
				|  |  | +        track.verify_revs = 1
 | 
	
		
			
				|  |  | +        return track
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +    def _find_sync(self, bits, sync, start):
 | 
	
		
			
				|  |  | +        for offs in bits.itersearch(sync):
 | 
	
		
			
				|  |  | +            if offs >= start:
 | 
	
		
			
				|  |  | +                return offs
 | 
	
		
			
				|  |  | +        return None
 | 
	
		
			
				|  |  | +    
 | 
	
		
			
				|  |  | +    def verify_track(self, flux):
 | 
	
		
			
				|  |  | +        flux.cue_at_index()
 | 
	
		
			
				|  |  | +        raw = RawTrack(clock = self.clock, data = flux)
 | 
	
		
			
				|  |  | +        bits, _ = raw.get_all_data()
 | 
	
		
			
				|  |  | +        weak_iter = it.chain(self.weak, [(self.verify_len+1,1)])
 | 
	
		
			
				|  |  | +        weak = next(weak_iter)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Start checking from the IAM sync
 | 
	
		
			
				|  |  | +        dump_start = self._find_sync(bits, mfm.iam_sync, 0)
 | 
	
		
			
				|  |  | +        self_start = self._find_sync(self.bits, mfm.iam_sync, 0)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Include the IAM pre-sync header
 | 
	
		
			
				|  |  | +        if dump_start is None:
 | 
	
		
			
				|  |  | +            return False
 | 
	
		
			
				|  |  | +        dump_start -= self.gap_presync * 16
 | 
	
		
			
				|  |  | +        self_start -= self.gap_presync * 16
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        while self_start is not None and dump_start is not None:
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # Find the weak areas immediately before and after the current
 | 
	
		
			
				|  |  | +            # region to be checked.
 | 
	
		
			
				|  |  | +            s,n = None,None
 | 
	
		
			
				|  |  | +            while self_start > weak[0]:
 | 
	
		
			
				|  |  | +                s,n = weak
 | 
	
		
			
				|  |  | +                weak = next(weak_iter)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # If there is a weak area preceding us, move the start point to
 | 
	
		
			
				|  |  | +            # immediately follow the weak area.
 | 
	
		
			
				|  |  | +            if s is not None:
 | 
	
		
			
				|  |  | +                delta = self_start - (s + n + 16)
 | 
	
		
			
				|  |  | +                self_start -= delta
 | 
	
		
			
				|  |  | +                dump_start -= delta
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # Truncate the region at the next weak area, or the last sector.
 | 
	
		
			
				|  |  | +            self_end = max(self_start, min(weak[0], self.verify_len+1))
 | 
	
		
			
				|  |  | +            dump_end = dump_start + self_end - self_start
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # Extract the corresponding areas from the pristine track and
 | 
	
		
			
				|  |  | +            # from the dump, and check that they match.
 | 
	
		
			
				|  |  | +            if bits[dump_start:dump_end] != self.bits[self_start:self_end]:
 | 
	
		
			
				|  |  | +                return False
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # Find the next A1A1A1 sync pattern
 | 
	
		
			
				|  |  | +            dump_start = self._find_sync(bits, mfm.sync, dump_end)
 | 
	
		
			
				|  |  | +            self_start = self._find_sync(self.bits, mfm.sync, self_end)
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +        # Did we verify all regions in the pristine track?
 | 
	
		
			
				|  |  | +        return self_start is None
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |  class EDSK(Image):
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  |      read_only = True
 | 
	
		
			
				|  |  |      default_format = 'ibm.mfm'
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | -    clock = 2e-6
 | 
	
		
			
				|  |  | -    time_per_rev = 0.2
 | 
	
		
			
				|  |  | -
 | 
	
		
			
				|  |  |      def __init__(self):
 | 
	
		
			
				|  |  |          self.to_track = dict()
 | 
	
		
			
				|  |  |  
 | 
	
		
			
				|  |  | +    # Currently only finds one weak range.
 | 
	
		
			
				|  |  | +    @staticmethod
 | 
	
		
			
				|  |  | +    def find_weak_ranges(dat, size):
 | 
	
		
			
				|  |  | +        orig = dat[:size]
 | 
	
		
			
				|  |  | +        s, e = size, 0
 | 
	
		
			
				|  |  | +        for i in range(1, len(dat)//size):
 | 
	
		
			
				|  |  | +            diff = [x^y for x, y in zip(orig, dat[size*i:size*(i+1)])]
 | 
	
		
			
				|  |  | +            weak = [idx for idx, val in enumerate(diff) if val != 0]
 | 
	
		
			
				|  |  | +            if weak:
 | 
	
		
			
				|  |  | +                s, e = min(s, weak[0]), max(e, weak[-1])
 | 
	
		
			
				|  |  | +        return [(s,e-s+1)] if s <= e else []
 | 
	
		
			
				|  |  | +        
 | 
	
		
			
				|  |  |      @classmethod
 | 
	
		
			
				|  |  |      def from_file(cls, name):
 | 
	
		
			
				|  |  |  
 | 
	
	
		
			
				|  | @@ -57,46 +146,91 @@ class EDSK(Image):
 | 
	
		
			
				|  |  |              error.check((cyl, head) not in edsk.to_track,
 | 
	
		
			
				|  |  |                          'EDSK: Track specified twice')
 | 
	
		
			
				|  |  |              while True:
 | 
	
		
			
				|  |  | -                track = mfm.IBM_MFM_Formatted(cyl, head)
 | 
	
		
			
				|  |  | -                track.clock = cls().clock
 | 
	
		
			
				|  |  | -                track.time_per_rev = cls().time_per_rev
 | 
	
		
			
				|  |  | -                pos = track.gap_4a
 | 
	
		
			
				|  |  | -                track.iams = [mfm.IAM(pos*16,(pos+4)*16)]
 | 
	
		
			
				|  |  | -                pos += 4 + track.gap_1
 | 
	
		
			
				|  |  | +                track = EDSKTrack()
 | 
	
		
			
				|  |  | +                t = bytes()
 | 
	
		
			
				|  |  | +                # Post-index gap
 | 
	
		
			
				|  |  | +                t += mfm.encode(bytes([track.gapbyte] * track.gap_4a))
 | 
	
		
			
				|  |  | +                # IAM
 | 
	
		
			
				|  |  | +                t += mfm.encode(bytes(track.gap_presync))
 | 
	
		
			
				|  |  | +                t += mfm.iam_sync_bytes
 | 
	
		
			
				|  |  | +                t += mfm.encode(bytes([mfm.IBM_MFM.IAM]))
 | 
	
		
			
				|  |  | +                t += mfm.encode(bytes([track.gapbyte] * track.gap_1))
 | 
	
		
			
				|  |  |                  secs = dat[o+24:o+24+8*nsecs]
 | 
	
		
			
				|  |  |                  data_pos = o + 256 # skip track header and sector-info table
 | 
	
		
			
				|  |  |                  while secs:
 | 
	
		
			
				|  |  |                      c, h, r, n, stat1, stat2, actual_length = struct.unpack(
 | 
	
		
			
				|  |  |                          '<6BH', secs[:8])
 | 
	
		
			
				|  |  |                      secs = secs[8:]
 | 
	
		
			
				|  |  | -                    pos += track.gap_presync
 | 
	
		
			
				|  |  | -                    idam = mfm.IDAM(pos*16, (pos+10)*16, 0,
 | 
	
		
			
				|  |  | -                                    c=c, h=h, r=r, n=n)
 | 
	
		
			
				|  |  | -                    pos += 10 + track.gap_2 + track.gap_presync
 | 
	
		
			
				|  |  |                      size = 128 << n
 | 
	
		
			
				|  |  | -                    error.check(size == actual_length,
 | 
	
		
			
				|  |  | -                                'EDSK: Weird sector size (copy protection?)')
 | 
	
		
			
				|  |  | +                    weak = []
 | 
	
		
			
				|  |  | +                    if size != actual_length:
 | 
	
		
			
				|  |  | +                        error.check(actual_length != 0
 | 
	
		
			
				|  |  | +                                    and actual_length % size == 0,
 | 
	
		
			
				|  |  | +                                    'EDSK: Weird sector size (GAP protection?)')
 | 
	
		
			
				|  |  | +                        weak = cls().find_weak_ranges(
 | 
	
		
			
				|  |  | +                            dat[data_pos:data_pos+actual_length], size)
 | 
	
		
			
				|  |  | +                    # Update CRCs according to status flags
 | 
	
		
			
				|  |  | +                    icrc, dcrc = 0, 0
 | 
	
		
			
				|  |  | +                    if stat1 & 0x20:
 | 
	
		
			
				|  |  | +                        if stat2 & 0x20:
 | 
	
		
			
				|  |  | +                            dcrc = 0xffff
 | 
	
		
			
				|  |  | +                        else:
 | 
	
		
			
				|  |  | +                            icrc = 0xffff
 | 
	
		
			
				|  |  | +                        stat1 &= ~0x20
 | 
	
		
			
				|  |  | +                        stat2 &= ~0x20
 | 
	
		
			
				|  |  | +                    # Update address marks according to status flags
 | 
	
		
			
				|  |  | +                    imark, dmark = mfm.IBM_MFM.IDAM, mfm.IBM_MFM.DAM
 | 
	
		
			
				|  |  | +                    if stat2 & 0x40:
 | 
	
		
			
				|  |  | +                        dmark = mfm.IBM_MFM.DDAM
 | 
	
		
			
				|  |  | +                    if stat2 & 0x01:
 | 
	
		
			
				|  |  | +                        dmark = 0
 | 
	
		
			
				|  |  | +                    elif stat1 & 0x01:
 | 
	
		
			
				|  |  | +                        imark = 0
 | 
	
		
			
				|  |  | +                    stat1 &= ~0x01
 | 
	
		
			
				|  |  | +                    stat2 &= ~0x41
 | 
	
		
			
				|  |  |                      error.check(stat1 == 0 and stat2 == 0,
 | 
	
		
			
				|  |  |                                  'EDSK: Mangled sector (copy protection?)')
 | 
	
		
			
				|  |  |                      sec_data = dat[data_pos:data_pos+size]
 | 
	
		
			
				|  |  | -                    dam = mfm.DAM(pos*16, (pos+4+size+2)*16, 0,
 | 
	
		
			
				|  |  | -                                  mark=track.DAM, data=sec_data)
 | 
	
		
			
				|  |  | -                    track.sectors.append(mfm.Sector(idam, dam))
 | 
	
		
			
				|  |  | -                    pos += 4 + size + 2 + gap_3
 | 
	
		
			
				|  |  | -                    data_pos += size
 | 
	
		
			
				|  |  | +                    data_pos += actual_length
 | 
	
		
			
				|  |  | +                    # IDAM
 | 
	
		
			
				|  |  | +                    t += mfm.encode(bytes(track.gap_presync))
 | 
	
		
			
				|  |  | +                    t += mfm.sync_bytes
 | 
	
		
			
				|  |  | +                    idam = bytes([0xa1, 0xa1, 0xa1, mfm.IBM_MFM.IDAM,
 | 
	
		
			
				|  |  | +                                  c, h, r, n])
 | 
	
		
			
				|  |  | +                    idam += struct.pack('>H', mfm.crc16.new(idam).crcValue)
 | 
	
		
			
				|  |  | +                    t += mfm.encode(idam[3:])
 | 
	
		
			
				|  |  | +                    t += mfm.encode(bytes([track.gapbyte] * track.gap_2))
 | 
	
		
			
				|  |  | +                    # DAM
 | 
	
		
			
				|  |  | +                    t += mfm.encode(bytes(track.gap_presync))
 | 
	
		
			
				|  |  | +                    t += mfm.sync_bytes
 | 
	
		
			
				|  |  | +                    track.weak += [((s+len(t)//2+4)*16, n*16) for s,n in weak]
 | 
	
		
			
				|  |  | +                    dam = bytes([0xa1, 0xa1, 0xa1, dmark]) + sec_data
 | 
	
		
			
				|  |  | +                    dam += struct.pack('>H', mfm.crc16.new(dam).crcValue^dcrc)
 | 
	
		
			
				|  |  | +                    t += mfm.encode(dam[3:])
 | 
	
		
			
				|  |  | +                    t += mfm.encode(bytes([track.gapbyte] * gap_3))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  |                  # Some EDSK images have bogus GAP3 values. If the track is too
 | 
	
		
			
				|  |  |                  # long to comfortably fit in 300rpm at double density, shrink
 | 
	
		
			
				|  |  |                  # GAP3 as far as necessary.
 | 
	
		
			
				|  |  |                  tracklen = int((track.time_per_rev / track.clock) / 16)
 | 
	
		
			
				|  |  | -                overhang = int(pos - tracklen*0.99)
 | 
	
		
			
				|  |  | +                overhang = int(len(t)//2 - tracklen*0.99)
 | 
	
		
			
				|  |  |                  if overhang <= 0:
 | 
	
		
			
				|  |  |                      break
 | 
	
		
			
				|  |  |                  new_gap_3 = gap_3 - math.ceil(overhang / nsecs)
 | 
	
		
			
				|  |  |                  error.check(new_gap_3 >= 0,
 | 
	
		
			
				|  |  |                              'EDSK: Track %d.%d is too long '
 | 
	
		
			
				|  |  |                              '(%d bits @ GAP3=%d; %d bits @ GAP3=0)'
 | 
	
		
			
				|  |  | -                            % (cyl, head, pos*16, gap_3, (pos-gap_3*nsecs)*16))
 | 
	
		
			
				|  |  | +                            % (cyl, head, len(t)*8, gap_3,
 | 
	
		
			
				|  |  | +                               (len(t)//2-gap_3*nsecs)*16))
 | 
	
		
			
				|  |  |                  gap_3 = new_gap_3
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            # Pre-index gap
 | 
	
		
			
				|  |  | +            track.verify_len = len(t)*8
 | 
	
		
			
				|  |  | +            gap = tracklen - len(t)//2
 | 
	
		
			
				|  |  | +            t += mfm.encode(bytes([track.gapbyte] * gap))
 | 
	
		
			
				|  |  | +
 | 
	
		
			
				|  |  | +            track.bits = bitarray(endian='big')
 | 
	
		
			
				|  |  | +            track.bits.frombytes(mfm.mfm_encode(t))
 | 
	
		
			
				|  |  |              edsk.to_track[cyl,head] = track
 | 
	
		
			
				|  |  |              o += tsize
 | 
	
		
			
				|  |  |  
 |