|
@@ -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
|
|
|
|