Эх сурвалжийг харах

Support read/write Acorn DFS (single density) images.

Keir Fraser 3 жил өмнө
parent
commit
c96ba1aa69

+ 22 - 0
scripts/greaseweazle/codec/formats.py

@@ -17,6 +17,26 @@ class Format:
         self.max_tracks = util.TrackSet(self.max_trackset)
         self.decode_track = self.fmt.decode_track
 
+class Format_Acorn_DFS_SS(Format):
+    img_compatible = True
+    default_trackset = 'c=0-39:h=0'
+    max_trackset = 'c=0-81:h=0'
+    def __init__(self):
+        import greaseweazle.codec.ibm.fm as m
+        self.fmt = m.Acorn_DFS
+        self.default_revs = m.default_revs
+        super().__init__()
+    
+class Format_Acorn_DFS_DS(Format):
+    img_compatible = True
+    default_trackset = 'c=0-39:h=0-1'
+    max_trackset = 'c=0-81:h=0-1'
+    def __init__(self):
+        import greaseweazle.codec.ibm.fm as m
+        self.fmt = m.Acorn_DFS
+        self.default_revs = m.default_revs
+        super().__init__()
+    
 class Format_Amiga_AmigaDOS_DD(Format):
     adf_compatible = True
     def __init__(self):
@@ -141,6 +161,8 @@ class Format_AtariST_880(Format):
     
     
 formats = {
+    'acorn.dfs.ss': Format_Acorn_DFS_SS,
+    'acorn.dfs.ds': Format_Acorn_DFS_DS,
     'amiga.amigados': Format_Amiga_AmigaDOS_DD,
     'amiga.amigados_hd': Format_Amiga_AmigaDOS_HD,
     'ibm.180': Format_IBM_180,

+ 342 - 0
scripts/greaseweazle/codec/ibm/fm.py

@@ -0,0 +1,342 @@
+# greaseweazle/codec/ibm/fm.py
+#
+# Written & released by Keir Fraser <keir.xen@gmail.com>
+#
+# This is free and unencumbered software released into the public domain.
+# See the file COPYING for more details, or visit <http://unlicense.org>.
+
+import binascii
+import copy, heapq, struct, functools
+import itertools as it
+from bitarray import bitarray
+import crcmod.predefined
+
+from greaseweazle.codec.ibm import mfm
+from greaseweazle.track import MasterTrack, RawTrack
+
+default_revs = 2
+
+def sync(dat, clk=0xc7):
+    x = 0
+    for i in range(8):
+        x <<= 1
+        x |= (clk >> (7-i)) & 1
+        x <<= 1
+        x |= (dat >> (7-i)) & 1
+    return bytes(struct.pack('>H', x))
+
+sync_prefix = bitarray(endian='big')
+sync_prefix.frombytes(b'\xaa\xaa' + sync(0xf8))
+sync_prefix = sync_prefix[:16+10]
+
+iam_sync_bytes = sync(0xfc, 0xd7)
+iam_sync = bitarray(endian='big')
+iam_sync.frombytes(b'\xaa\xaa' + iam_sync_bytes)
+
+crc16 = crcmod.predefined.Crc('crc-ccitt-false')
+
+sec_sz = mfm.sec_sz
+IDAM   = mfm.IDAM
+DAM    = mfm.DAM
+Sector = mfm.Sector
+IAM    = mfm.IAM
+    
+class IBM_FM:
+
+    IAM  = 0xfc
+    IDAM = 0xfe
+    DAM  = 0xfb
+    DDAM = 0xf8
+
+    gap_presync = 6
+
+    gapbyte = 0xff
+
+    def __init__(self, cyl, head):
+        self.cyl, self.head = cyl, head
+        self.sectors = []
+        self.iams = []
+
+    def summary_string(self):
+        nsec, nbad = len(self.sectors), self.nr_missing()
+        s = "IBM FM (%d/%d sectors)" % (nsec - nbad, nsec)
+        if nbad != 0:
+            s += " - %d sectors missing" % nbad
+        return s
+
+    def has_sec(self, sec_id):
+        return self.sectors[sec_id].crc == 0
+
+    def nr_missing(self):
+        return len(list(filter(lambda x: x.crc != 0, self.sectors)))
+
+    def flux_for_writeout(self, *args, **kwargs):
+        return self.raw_track().flux_for_writeout(args, kwargs)
+
+    def flux(self, *args, **kwargs):
+        return self.raw_track().flux(args, kwargs)
+
+
+    def decode_raw(self, track):
+        track.cue_at_index()
+        raw = RawTrack(clock = self.clock, data = track)
+        bits, _ = raw.get_all_data()
+
+        areas = []
+        idam = None
+
+        ## 1. Calculate offsets within dump
+        
+        for offs in bits.itersearch(iam_sync):
+            offs += 16
+            areas.append(IAM(offs, offs+1*16))
+            self.has_iam = True
+
+        for offs in bits.itersearch(sync_prefix):
+            offs += 16
+            mark = decode(bits[offs:offs+1*16].tobytes())[0]
+            clock = decode(bits[offs-1:offs+1*16-1].tobytes())[0]
+            if clock != 0xc7:
+                continue
+            if mark == IBM_FM.IDAM:
+                s, e = offs, offs+7*16
+                b = decode(bits[s:e].tobytes())
+                c,h,r,n = struct.unpack(">x4B2x", b)
+                crc = crc16.new(b).crcValue
+                if idam is not None:
+                    areas.append(idam)
+                idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
+            elif mark == IBM_FM.DAM or mark == IBM_FM.DDAM:
+                if idam is None or idam.end - offs > 1000:
+                    areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
+                else:
+                    sz = 128 << idam.n
+                    s, e = offs, offs+(1+sz+2)*16
+                    b = decode(bits[s:e].tobytes())
+                    crc = crc16.new(b).crcValue
+                    dam = DAM(s, e, crc, mark=mark, data=b[1:-2])
+                    areas.append(Sector(idam, dam))
+                idam = None
+            else:
+                pass #print("Unknown mark %02x" % mark)
+
+        if idam is not None:
+            areas.append(idam)
+
+        # Convert to offsets within track
+        areas.sort(key=lambda x:x.start)
+        index = iter(raw.revolutions)
+        p, n = 0, next(index)
+        for a in areas:
+            if a.start >= n:
+                p = n
+                try:
+                    n = next(index)
+                except StopIteration:
+                    n = float('inf')
+            a.delta(p)
+        areas.sort(key=lambda x:x.start)
+
+        # Add to the deduped lists
+        for a in areas:
+            match = False
+            if isinstance(a, IAM):
+                list = self.iams
+            elif isinstance(a, Sector):
+                list = self.sectors
+            else:
+                continue
+            for s in list:
+                if abs(s.start - a.start) < 1000:
+                    match = True
+                    break
+            if match and isinstance(a, Sector) and s.crc != 0 and a.crc == 0:
+                self.sectors = [x for x in self.sectors if x != a]
+                match = False
+            if not match:
+                list.append(a)
+
+
+    def raw_track(self):
+
+        areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
+        t = bytes()
+
+        for a in areas:
+            start = a.start//16 - self.gap_presync
+            gap = max(start - len(t)//2, 0)
+            t += encode(bytes([self.gapbyte] * gap))
+            t += encode(bytes(self.gap_presync))
+            if isinstance(a, IAM):
+                t += iam_sync_bytes
+            elif isinstance(a, Sector):
+                idam = bytes([self.IDAM,
+                              a.idam.c, a.idam.h, a.idam.r, a.idam.n])
+                idam += struct.pack('>H', crc16.new(idam).crcValue)
+                t += sync(idam[0]) + encode(idam[1:])
+                start = a.dam.start//16 - self.gap_presync
+                gap = max(start - len(t)//2, 0)
+                t += encode(bytes([self.gapbyte] * gap))
+                t += encode(bytes(self.gap_presync))
+                dam = bytes([a.dam.mark]) + a.dam.data
+                dam += struct.pack('>H', crc16.new(dam).crcValue)
+                t += sync(dam[0]) + encode(dam[1:])
+
+        # Add the pre-index gap.
+        tlen = int((self.time_per_rev / self.clock) // 16)
+        gap = max(tlen - len(t)//2, 0)
+        t += encode(bytes([self.gapbyte] * gap))
+
+        track = MasterTrack(
+            bits = t,
+            time_per_rev = self.time_per_rev)
+        track.verify = self
+        track.verify_revs = default_revs
+        return track
+
+
+class IBM_FM_Formatted(IBM_FM):
+
+    gap_4a = 40 # Post-Index
+    gap_1  = 26 # Post-IAM
+    gap_2  = 11 # Post-IDAM
+
+    def __init__(self, cyl, head):
+
+        super().__init__(cyl, head)
+        self.raw_iams, self.raw_sectors = [], []
+
+    def decode_raw(self, track):
+        iams, sectors = self.iams, self.sectors
+        self.iams, self.sectors = self.raw_iams, self.raw_sectors
+        super().decode_raw(track)
+        self.iams, self.sectors = iams, sectors
+        for r in self.raw_sectors:
+            if r.idam.crc != 0:
+                continue
+            for s in self.sectors:
+                if (s.idam.c == r.idam.c and
+                    s.idam.h == r.idam.h and
+                    s.idam.r == r.idam.r and
+                    s.idam.n == r.idam.n):
+                    s.idam.crc = 0
+                    if r.dam.crc == 0 and s.dam.crc != 0:
+                        s.dam.crc = s.crc = 0
+                        s.dam.data = r.dam.data
+
+    def set_img_track(self, tdat):
+        pos = 0
+        self.sectors.sort(key = lambda x: x.idam.r)
+        totsize = functools.reduce(lambda x, y: x + (128<<y.idam.n),
+                                   self.sectors, 0)
+        if len(tdat) < totsize:
+            tdat += bytes(totsize - len(tdat))
+        for s in self.sectors:
+            s.crc = s.idam.crc = s.dam.crc = 0
+            size = 128 << s.idam.n
+            s.dam.data = tdat[pos:pos+size]
+            pos += size
+        self.sectors.sort(key = lambda x: x.start)
+        return totsize
+
+    def get_img_track(self):
+        tdat = bytearray()
+        sectors = self.sectors.copy()
+        sectors.sort(key = lambda x: x.idam.r)
+        for s in sectors:
+            tdat += s.dam.data
+        return tdat
+        
+    def verify_track(self, flux):
+        readback_track = IBM_FM_Formatted(self.cyl, self.head)
+        readback_track.clock = self.clock
+        readback_track.time_per_rev = self.time_per_rev
+        for x in self.iams:
+            readback_track.iams.append(copy.copy(x))
+        for x in self.sectors:
+            idam, dam = copy.copy(x.idam), copy.copy(x.dam)
+            idam.crc, dam.crc = 0xffff, 0xffff
+            readback_track.sectors.append(Sector(idam, dam))
+        readback_track.decode_raw(flux)
+        if readback_track.nr_missing() != 0:
+            return False
+        return self.sectors == readback_track.sectors
+
+
+class IBM_FM_Predefined(IBM_FM_Formatted):
+
+    cskew = 0
+    hskew = 0
+    interleave = 1
+    
+    def __init__(self, cyl, head):
+
+        super().__init__(cyl, head)
+
+        # Create logical sector map in rotational order
+        sec_map = [-1] * self.nsec
+        pos = (cyl*self.cskew + head*self.hskew) % self.nsec
+        for i in range(self.nsec):
+            while sec_map[pos] != -1:
+                pos = (pos + 1) % self.nsec
+            sec_map[pos] = i
+            pos = (pos + self.interleave) % self.nsec
+
+        pos = self.gap_4a
+        if self.gap_1 is not None:
+            self.iams = [IAM(pos*16,(pos+1)*16)]
+            pos += 1 + self.gap_1
+
+        for i in range(self.nsec):
+            pos += self.gap_presync
+            idam = IDAM(pos*16, (pos+7)*16, 0xffff,
+                        c=cyl, h=head, r=self.id0+sec_map[i], n = self.sz)
+            pos += 7 + self.gap_2 + self.gap_presync
+            size = 128 << self.sz
+            dam = DAM(pos*16, (pos+1+size+2)*16, 0xffff,
+                      mark=self.DAM, data=bytes(size))
+            self.sectors.append(Sector(idam, dam))
+            pos += 1 + size + 2 + self.gap_3
+
+    @classmethod
+    def decode_track(cls, cyl, head, track):
+        mfm = cls(cyl, head)
+        mfm.decode_raw(track)
+        return mfm
+
+
+class Acorn_DFS(IBM_FM_Predefined):
+
+    time_per_rev = 0.2
+    clock = 4e-6
+
+    gap_1  = 0 # No IAM
+    gap_3  = 21
+    nsec   = 10
+    id0    = 0
+    sz     = 1
+    cskew  = 3
+
+
+encode_list = []
+for x in range(256):
+    y = 0
+    for i in range(8):
+        y <<= 1
+        y |= 1
+        y <<= 1
+        y |= (x >> (7-i)) & 1
+    encode_list.append(y)
+
+def encode(dat):
+    out = bytearray()
+    for x in dat:
+        out += struct.pack('>H', encode_list[x])
+    return bytes(out)
+
+decode = mfm.decode
+
+
+# Local variables:
+# python-indent: 4
+# End:

+ 15 - 0
scripts/greaseweazle/image/dsd.py

@@ -0,0 +1,15 @@
+# greaseweazle/image/dsd.py
+#
+# Written & released by Keir Fraser <keir.xen@gmail.com>
+#
+# This is free and unencumbered software released into the public domain.
+# See the file COPYING for more details, or visit <http://unlicense.org>.
+
+from greaseweazle.image.img import IMG
+
+class DSD(IMG):
+    default_format = 'acorn.dfs.ds'
+
+# Local variables:
+# python-indent: 4
+# End:

+ 15 - 0
scripts/greaseweazle/image/ssd.py

@@ -0,0 +1,15 @@
+# greaseweazle/image/ssd.py
+#
+# Written & released by Keir Fraser <keir.xen@gmail.com>
+#
+# This is free and unencumbered software released into the public domain.
+# See the file COPYING for more details, or visit <http://unlicense.org>.
+
+from greaseweazle.image.img import IMG
+
+class SSD(IMG):
+    default_format = 'acorn.dfs.ss'
+
+# Local variables:
+# python-indent: 4
+# End:

+ 2 - 0
scripts/greaseweazle/tools/util.py

@@ -161,6 +161,7 @@ def split_opts(seq):
 def get_image_class(name):
     image_types = { '.adf': 'ADF',
                     '.d81': 'D81',
+                    '.dsd': 'DSD',
                     '.dsk': 'EDSK',
                     '.hfe': 'HFE',
                     '.ima': 'IMG',
@@ -168,6 +169,7 @@ def get_image_class(name):
                     '.ipf': 'IPF',
                     '.raw': 'KryoFlux',
                     '.scp': 'SCP',
+                    '.ssd': 'SSD',
                     '.st' : 'IMG' }
     if os.path.isdir(name):
         typename = 'KryoFlux'