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

IPF: Initial support for writing IPF images

Keir Fraser 5 жил өмнө
parent
commit
8e28f04d14

+ 6 - 3
scripts/greaseweazle/flux.py

@@ -24,16 +24,19 @@ class Flux:
 
 
     @classmethod
-    def from_bitarray(cls, bitarray, bitrate):
+    def from_bitarray(cls, bitarray, bitrate, timing=None):
+        if not timing:
+            timing = [1000] * len(bitarray)
+        timing_i = iter(timing)
         flux_list = []
         count = 0
         for bit in bitarray:
-            count += 1
+            count += next(timing_i)
             if bit:
                 flux_list.append(count)
                 count = 0
         flux_list[0] += count
-        return Flux([sum(flux_list)], flux_list, bitrate)
+        return Flux([sum(flux_list)], flux_list, bitrate * 1000)
 
  
 # Local variables:

+ 6 - 0
scripts/greaseweazle/image/hfe.py

@@ -22,6 +22,12 @@ class HFE:
         self.track_list = []
 
 
+    @classmethod
+    def to_file(cls, start_cyl, nr_sides):
+        hfe = cls(start_cyl, nr_sides)
+        return hfe
+
+
     @classmethod
     def from_file(cls, dat):
 

+ 275 - 0
scripts/greaseweazle/image/ipf.py

@@ -0,0 +1,275 @@
+# greaseweazle/image/ipf.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 os
+import platform
+import ctypes as ct
+from bitarray import bitarray
+from greaseweazle.flux import Flux
+
+class CapsDateTimeExt(ct.Structure):
+    _pack_ = 1
+    _fields_ = [
+        ('year',  ct.c_uint),
+        ('month', ct.c_uint),
+        ('day',   ct.c_uint),
+        ('hour',  ct.c_uint),
+        ('min',   ct.c_uint),
+        ('sec',   ct.c_uint),
+        ('tick',  ct.c_uint)]
+
+class CapsImageInfo(ct.Structure):
+    platform_name = [
+        "N/A", "Amiga", "Atari ST", "IBM PC", "Amstrad CPC",
+        "Spectrum", "Sam Coupe", "Archimedes", "C64", "Atari (8-bit)" ]
+    _pack_ = 1
+    _fields_ = [
+        ('type',        ct.c_uint), # image type
+        ('release',     ct.c_uint), # release ID
+        ('revision',    ct.c_uint), # release revision ID
+        ('mincylinder', ct.c_uint), # lowest cylinder number
+        ('maxcylinder', ct.c_uint), # highest cylinder number
+        ('minhead',     ct.c_uint), # lowest head number
+        ('maxhead',     ct.c_uint), # highest head number
+        ('crdt',        CapsDateTimeExt), # image creation date.time
+        ('platform',    ct.c_uint * 4)] # intended platform(s)
+
+class CapsTrackInfoT2(ct.Structure):
+    _pack_ = 1
+    _fields_ = [
+        ('type',       ct.c_uint), # track type
+        ('cylinder',   ct.c_uint), # cylinder#
+        ('head',       ct.c_uint), # head#
+        ('sectorcnt',  ct.c_uint), # available sectors
+        ('sectorsize', ct.c_uint), # sector size, unused
+        ('trackbuf',   ct.POINTER(ct.c_ubyte)), # track buffer memory 
+        ('tracklen',   ct.c_uint), # track buffer memory length
+        ('timelen',    ct.c_uint), # timing buffer length
+        ('timebuf',    ct.POINTER(ct.c_uint)), # timing buffer
+        ('overlap',    ct.c_int),  # overlap position
+        ('startbit',   ct.c_uint), # start position of the decoding
+        ('wseed',      ct.c_uint), # weak bit generator data
+        ('weakcnt',    ct.c_uint)] # number of weak data areas
+
+class CapsSectorInfo(ct.Structure):
+    _pack_ = 1
+    _fields_ = [
+        ('descdatasize', ct.c_uint), # data size in bits from IPF descriptor
+        ('descgapsize', ct.c_uint),  # gap size in bits from IPF descriptor
+        ('datasize', ct.c_uint),     # data size in bits from decoder
+        ('gapsize', ct.c_uint),      # gap size in bits from decoder
+        ('datastart', ct.c_uint),    # data start pos in bits from decoder
+        ('gapstart', ct.c_uint),     # gap start pos in bits from decoder
+        ('gapsizews0', ct.c_uint),   # gap size before write splice
+        ('gapsizews1', ct.c_uint),   # gap size after write splice
+        ('gapws0mode', ct.c_uint),   # gap size mode before write splice
+        ('gapws1mode', ct.c_uint),   # gap size mode after write splice
+        ('celltype', ct.c_uint),     # bitcell type
+        ('enctype', ct.c_uint)]      # encoder type
+
+class CapsDataInfo(ct.Structure):
+    _pack_ = 1
+    _fields_ = [
+        ('type', ct.c_uint),  # data type
+        ('start', ct.c_uint), # start position
+        ('size', ct.c_uint)]  # size in bits
+
+class DI_LOCK:
+    INDEX     = 1<<0
+    ALIGN     = 1<<1
+    DENVAR    = 1<<2
+    DENAUTO   = 1<<3
+    DENNOISE  = 1<<4
+    NOISE     = 1<<5
+    NOISEREV  = 1<<6
+    MEMREF    = 1<<7
+    UPDATEFD  = 1<<8
+    TYPE      = 1<<9
+    DENALT    = 1<<10
+    OVLBIT    = 1<<11
+    TRKBIT    = 1<<12
+    NOUPDATE  = 1<<13
+    SETWSEED  = 1<<14
+    def_flags = (DENVAR | UPDATEFD | TYPE | OVLBIT | TRKBIT)
+
+
+class IPF:
+
+    def __init__(self, start_cyl, nr_sides):
+        self.lib = get_libcaps()
+        self.start_cyl = start_cyl
+        self.nr_sides = nr_sides
+
+    def __del__(self):
+        try:
+            self.lib.CAPSUnlockAllTracks(self.iid)
+            self.lib.CAPSUnlockImage(self.iid)
+            self.lib.CAPSRemImage(self.iid)
+            del(self.iid)
+        except AttributeError:
+            pass
+
+    def __str__(self):
+        pi = self.pi
+        s = "IPF Image File:"
+        s += "\n SPS ID: %04d (rev %d)" % (pi.release, pi.revision)
+        s += "\n Platform: "
+        nr_platforms = 0
+        for p in pi.platform:
+            if p == 0 and nr_platforms != 0:
+                break
+            if nr_platforms > 0:
+                s += ", "
+            s += pi.platform_name[p]
+            nr_platforms += 1
+        s += ("\n Created: %d/%d/%d %02d:%02d:%02d"
+             % (pi.crdt.year, pi.crdt.month, pi.crdt.day,
+                pi.crdt.hour, pi.crdt.min, pi.crdt.sec))
+        s += ("\n Cyls: %d-%d  Heads: %d-%d"
+              % (pi.mincylinder, pi.maxcylinder, pi.minhead, pi.maxhead))
+        return s
+
+    @classmethod
+    def from_filename(cls, name):
+
+        ipf = cls(0, 0)
+
+        ipf.iid = ipf.lib.CAPSAddImage()
+        assert ipf.iid >= 0, "Could not create IPF image container"
+        cname = ct.c_char_p(name.encode())
+        res = ipf.lib.CAPSLockImage(ipf.iid, cname)
+        assert res == 0, "Could not open IPF image '%s'" % name
+        res = ipf.lib.CAPSLoadImage(ipf.iid, DI_LOCK.def_flags)
+        assert res == 0, "Could not load IPF image '%s'" % name
+        ipf.pi = CapsImageInfo()
+        res = ipf.lib.CAPSGetImageInfo(ct.byref(ipf.pi), ipf.iid)
+        assert res == 0
+        print(ipf)
+
+        return ipf
+
+
+    def get_track(self, cyl, head, writeout=False):
+        pi = self.pi
+        if head < pi.minhead or head > pi.maxhead:
+            return None
+        if cyl < pi.mincylinder or cyl > pi.maxcylinder:
+            return None
+
+        ti = CapsTrackInfoT2(2)
+        res = self.lib.CAPSLockTrack(ct.byref(ti), self.iid,
+                                     cyl, head, DI_LOCK.def_flags)
+        assert res == 0
+
+        if not ti.trackbuf:
+            return None # unformatted/empty
+        carray_type = ct.c_ubyte * ((ti.tracklen+7)//8)
+        carray = carray_type.from_address(
+            ct.addressof(ti.trackbuf.contents))
+        trackbuf = bitarray(endian='big')
+        trackbuf.frombytes(bytes(carray))
+        trackbuf = trackbuf[:ti.tracklen]
+
+        # Write splice is at trackbuf[ti.overlap]. Index is at trackbuf[0].
+        #for i in range(ti.sectorcnt):
+        #    si = CapsSectorInfo()
+        #    res = self.lib.CAPSGetInfo(ct.byref(si), self.iid,
+        #                               cyl, head, 1, i)
+        #    assert res == 0
+        #    # Data is at trackbuf[si.datastart:si.datastart + si.datasize]
+        #for i in range(ti.weakcnt):
+        #    wi = CapsDataInfo()
+        #    res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid,
+        #                               cyl, head, 2, i)
+        #    assert res == 0
+        #    # Weak data at trackbuf[wi.start:wi.start + wi.size]
+
+        assert ti.weakcnt == 0, "Can't yet handle weak data"
+
+        # We don't really have access to the bitrate. It depends on RPM.
+        # So we assume a rotation rate of 300 RPM (5 rev/sec).
+        bitrate = ti.tracklen * 5
+
+        timebuf = None
+        if ti.timebuf:
+            carray_type = ct.c_uint * ti.timelen
+            carray = carray_type.from_address(
+                ct.addressof(ti.timebuf.contents))
+            # Unpack the per-byte timing info into per-bitcell
+            timebuf = []
+            for i in carray:
+                for j in range(8):
+                    timebuf.append(i)
+            # Pad the timing info with normal cell lengths as necessary
+            for j in range(len(carray)*8, ti.tracklen):
+                timebuf.append(1000)
+            # Clip the timing info, if necessary.
+            timebuf = timebuf[:ti.tracklen]
+
+        # TODO: Place overlap (write splice) at the correct position.
+        if ti.overlap != 0:
+            trackbuf = trackbuf[ti.overlap:] + trackbuf[:ti.overlap]
+            if timebuf:
+                timebuf = timebuf[ti.overlap:] + timebuf[:ti.overlap]
+
+        return Flux.from_bitarray(trackbuf, bitrate, timebuf)
+
+
+# Open and initialise the CAPS library.
+def open_libcaps():
+
+    # Get the OS-dependent list of valid CAPS library names.
+    _names = []
+    if platform.system() == "Linux":
+        _names = [ "libcapsimage.so.5", "libcapsimage.so.5.1",
+                   "libcapsimage.so.4", "libcapsimage.so.4.2",
+                   "libcapsimage.so" ]
+    elif platform.system() == "Darwin":
+        _names = [ "CAPSImage.framework/CAPSImage" ]
+    elif platform.system() == "Windows":
+        _names = [ "CAPSImg_x64.dll", "CAPSImg.dll" ]
+
+    # Get the absolute path to the root Greaseweazle folder.
+    path = os.path.dirname(os.path.abspath(__file__))
+    for _ in range(3):
+        path = os.path.join(path, os.pardir)
+    path = os.path.normpath(path)
+
+    # Create a search list of both relative and absolute library names.
+    names = []
+    for name in _names:
+        names.append(name)
+        names.append(os.path.join(path, name))
+
+    # Walk the search list, trying to open the CAPS library.
+    for name in names:
+        try:
+            lib = ct.cdll.LoadLibrary(name)
+            break
+        except:
+            pass
+    assert "lib" in locals(), "Could not find SPS/CAPS IPF decode library"
+    print(name)
+    
+    # We have opened the library. Now initialise it.
+    res = lib.CAPSInit()
+    assert res == 0, "Failure initialising %s" % libname
+
+    return lib
+
+
+# Get a reference to the CAPS library. Open it if necessary.
+def get_libcaps():
+    global libcaps
+    if not 'libcaps' in globals():
+        libcaps = open_libcaps()
+    return libcaps
+
+
+# Local variables:
+# python-indent: 4
+# End:

+ 6 - 0
scripts/greaseweazle/image/scp.py

@@ -21,6 +21,12 @@ class SCP:
         self.track_list = []
 
 
+    @classmethod
+    def to_file(cls, start_cyl, nr_sides):
+        hfe = cls(start_cyl, nr_sides)
+        return hfe
+
+
     @classmethod
     def from_file(cls, dat):
 

+ 5 - 1
scripts/greaseweazle/tools/read.py

@@ -19,7 +19,11 @@ def read_to_image(usb, args):
     image_class = util.get_image_class(args.file)
     if not image_class:
         return
-    image = image_class(args.scyl, args.nr_sides)
+    if not hasattr(image_class, 'to_file'):
+        print("%s: Cannot create %s image files"
+              % (args.file, image_class.__name__))
+        return
+    image = image_class.to_file(args.scyl, args.nr_sides)
 
     for cyl in range(args.scyl, args.ecyl+1):
         for side in range(0, args.nr_sides):

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

@@ -14,6 +14,7 @@ from greaseweazle import version
 from greaseweazle import usb as USB
 from greaseweazle.image.scp import SCP
 from greaseweazle.image.hfe import HFE
+from greaseweazle.image.ipf import IPF
 
 
 def drive_letter(letter):
@@ -30,10 +31,10 @@ def drive_letter(letter):
 
 
 def get_image_class(name):
-    image_types = { '.scp': SCP, '.hfe': HFE }
+    image_types = { '.scp': SCP, '.hfe': HFE, '.ipf': IPF }
     _, ext = os.path.splitext(name)
     if not ext.lower() in image_types:
-        print("**Error: Unrecognised file suffix '%s'" % ext)
+        print("%s: Unrecognised file suffix '%s'" % (name, ext))
         return None
     return image_types[ext.lower()]
 

+ 5 - 2
scripts/greaseweazle/tools/write.py

@@ -26,8 +26,11 @@ def write_from_image(usb, args):
     image_class = util.get_image_class(args.file)
     if not image_class:
         return
-    with open(args.file, "rb") as f:
-        image = image_class.from_file(f.read())
+    if hasattr(image_class, 'from_filename'):
+        image = image_class.from_filename(args.file)
+    else:
+        with open(args.file, "rb") as f:
+            image = image_class.from_file(f.read())
 
     for cyl in range(args.scyl, args.ecyl+1):
         for side in range(0, args.nr_sides):