# greaseweazle/image/scp.py # # Written & released by Keir Fraser # # This is free and unencumbered software released into the public domain. # See the file COPYING for more details, or visit . import struct, functools from greaseweazle import error from greaseweazle.flux import Flux from .image import Image # Names for disktype byte in SCP file header DiskType = { 'amiga': 0x04, 'c64': 0x00 } class SCPOpts: """legacy_ss: Set to True to generate (incorrect) legacy single-sided SCP image. """ def __init__(self): self.legacy_ss = False self._disktype = 0x80 # Other @property def disktype(self): return self._disktype @disktype.setter def disktype(self, disktype): try: self._disktype = DiskType[disktype.lower()] except KeyError: try: self._disktype = int(disktype, 0) except ValueError: raise error.Fatal("Bad SCP disktype: '%s'" % disktype) class SCPTrack: def __init__(self, tdh, dat, splice=None): self.tdh = tdh self.dat = dat self.splice = splice class SCP(Image): # 40MHz sample_freq = 40000000 def __init__(self): self.opts = SCPOpts() self.nr_revs = None self.to_track = dict() self.index_cued = True def side_count(self): s = [0,0] # non-empty tracks on each side for tnr in self.to_track: s[tnr&1] += 1 return s @classmethod def from_file(cls, name): splices = None with open(name, "rb") as f: dat = f.read() header = struct.unpack("<3s9BI", dat[0:16]) (sig, _, _, nr_revs, _, _, flags, _, single_sided, _, _) = header error.check(sig == b"SCP", "SCP: Bad signature") index_cued = flags & 1 or nr_revs == 1 # Some tools generate a short TLUT. We handle this by truncating the # TLUT at the first Track Data Header. trk_offs = struct.unpack("<168I", dat[16:0x2b0]) for i in range(168): try: off = trk_offs[i] except IndexError: break if off == 0 or off >= 0x2b0: continue off = off//4 - 4 error.check(off >= 0, "SCP: Bad Track Table") trk_offs = trk_offs[:off] # Parse the extension block introduced by github:markusC64/g64conv. # b'EXTS', length, # Extension Area contains consecutive chunks of the form: # ID, length, ext_sig, ext_len = struct.unpack('<4sI', dat[0x2b0:0x2b8]) min_tdh = min(filter(lambda x: x != 0, trk_offs), default=0) if ext_sig == b'EXTS' and 0x2b8 + ext_len <= min_tdh: pos, end = 0x2b8, 0x2b8 + ext_len while end - pos >= 8: chk_sig, chk_len = struct.unpack('<4sI', dat[pos:pos+8]) pos += 8 # WRSP: WRite SPlice information block. # Data is comprised of >= 169 32-bit values: # 0: Flags (currently unused; must be zero) # N: Write splice/overlap position for track N, in SCP ticks # (zero if the track is unused) if chk_sig == b'WRSP' and chk_len >= 169*4: # Write-splice positions for writing out SCP tracks # correctly to disk. splices = struct.unpack('<168I', dat[pos+4:pos+169*4]) pos += chk_len scp = cls() scp.nr_revs = nr_revs if not index_cued: scp.nr_revs -= 1 for trknr in range(len(trk_offs)): trk_off = trk_offs[trknr] if trk_off == 0: continue # Parse the SCP track header and extract the flux data. thdr = dat[trk_off:trk_off+4+12*nr_revs] sig, tnr = struct.unpack("<3sB", thdr[:4]) error.check(sig == b"TRK", "SCP: Missing track signature") error.check(tnr == trknr, "SCP: Wrong track number in header") thdr = thdr[4:] # Remove TRK header if not index_cued: # Remove first partial revolution thdr = thdr[12:] s_off, = struct.unpack("= nr_revs: # We're done: We simply discard any surplus flux samples self.to_track[cyl*2+side] = SCPTrack(tdh, dat) return to_index += flux.index_list[rev] # Process the current flux sample into SCP "bitcell" format to_index -= x y = x * factor + rem val = round(y) if (val & 65535) == 0: val += 1 rem = y - val while val >= 65536: dat.append(0) dat.append(0) val -= 65536 dat.append(val>>8) dat.append(val&255) # Header for last track(s) in case we ran out of flux timings. while rev < nr_revs: tdh += struct.pack("