edsk.py 9.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. # greaseweazle/image/edsk.py
  2. #
  3. # Written & released by Keir Fraser <keir.xen@gmail.com>
  4. #
  5. # This is free and unencumbered software released into the public domain.
  6. # See the file COPYING for more details, or visit <http://unlicense.org>.
  7. import binascii, math, struct
  8. import itertools as it
  9. from bitarray import bitarray
  10. from greaseweazle import error
  11. from greaseweazle.codec.ibm import mfm
  12. from greaseweazle.track import MasterTrack, RawTrack
  13. from .image import Image
  14. class EDSKTrack:
  15. gap_presync = 12
  16. gap_4a = 80 # Post-Index
  17. gap_1 = 50 # Post-IAM
  18. gap_2 = 22 # Post-IDAM
  19. gapbyte = 0x4e
  20. def __init__(self):
  21. self.time_per_rev = 0.2
  22. self.clock = 2e-6
  23. self.bits, self.weak = [], []
  24. def raw_track(self):
  25. track = MasterTrack(
  26. bits = self.bits,
  27. time_per_rev = self.time_per_rev,
  28. weak = self.weak)
  29. track.verify = self
  30. track.verify_revs = 1
  31. return track
  32. def _find_sync(self, bits, sync, start):
  33. for offs in bits.itersearch(sync):
  34. if offs >= start:
  35. return offs
  36. return None
  37. def verify_track(self, flux):
  38. flux.cue_at_index()
  39. raw = RawTrack(clock = self.clock, data = flux)
  40. bits, _ = raw.get_all_data()
  41. weak_iter = it.chain(self.weak, [(self.verify_len+1,1)])
  42. weak = next(weak_iter)
  43. # Start checking from the IAM sync
  44. dump_start = self._find_sync(bits, mfm.iam_sync, 0)
  45. self_start = self._find_sync(self.bits, mfm.iam_sync, 0)
  46. # Include the IAM pre-sync header
  47. if dump_start is None:
  48. return False
  49. dump_start -= self.gap_presync * 16
  50. self_start -= self.gap_presync * 16
  51. while self_start is not None and dump_start is not None:
  52. # Find the weak areas immediately before and after the current
  53. # region to be checked.
  54. s,n = None,None
  55. while self_start > weak[0]:
  56. s,n = weak
  57. weak = next(weak_iter)
  58. # If there is a weak area preceding us, move the start point to
  59. # immediately follow the weak area.
  60. if s is not None:
  61. delta = self_start - (s + n + 16)
  62. self_start -= delta
  63. dump_start -= delta
  64. # Truncate the region at the next weak area, or the last sector.
  65. self_end = max(self_start, min(weak[0], self.verify_len+1))
  66. dump_end = dump_start + self_end - self_start
  67. # Extract the corresponding areas from the pristine track and
  68. # from the dump, and check that they match.
  69. if bits[dump_start:dump_end] != self.bits[self_start:self_end]:
  70. return False
  71. # Find the next A1A1A1 sync pattern
  72. dump_start = self._find_sync(bits, mfm.sync, dump_end)
  73. self_start = self._find_sync(self.bits, mfm.sync, self_end)
  74. # Did we verify all regions in the pristine track?
  75. return self_start is None
  76. class EDSK(Image):
  77. read_only = True
  78. default_format = 'ibm.mfm'
  79. def __init__(self):
  80. self.to_track = dict()
  81. # Currently only finds one weak range.
  82. @staticmethod
  83. def find_weak_ranges(dat, size):
  84. orig = dat[:size]
  85. s, e = size, 0
  86. for i in range(1, len(dat)//size):
  87. diff = [x^y for x, y in zip(orig, dat[size*i:size*(i+1)])]
  88. weak = [idx for idx, val in enumerate(diff) if val != 0]
  89. if weak:
  90. s, e = min(s, weak[0]), max(e, weak[-1])
  91. return [(s,e-s+1)] if s <= e else []
  92. @classmethod
  93. def from_file(cls, name):
  94. with open(name, "rb") as f:
  95. dat = f.read()
  96. edsk = cls()
  97. sig, creator, ncyls, nsides, track_sz = struct.unpack(
  98. '<34s14s2BH', dat[:52])
  99. if sig[:8] == b'MV - CPC':
  100. extended = False
  101. elif sig[:16] == b'EXTENDED CPC DSK':
  102. extended = True
  103. else:
  104. raise error.Fatal('Unrecognised CPC DSK file: bad signature')
  105. if extended:
  106. tsizes = list(dat[52:52+ncyls*nsides])
  107. tsizes = list(map(lambda x: x*256, tsizes))
  108. else:
  109. raise error.Fatal('Standard CPC DSK file not yet supported')
  110. o = 256 # skip disk header and track-size table
  111. for tsize in tsizes:
  112. if tsize == 0:
  113. continue
  114. sig, cyl, head, sec_sz, nsecs, gap_3, filler = struct.unpack(
  115. '<12s4x2B2x4B', dat[o:o+24])
  116. error.check(sig == b'Track-Info\r\n',
  117. 'EDSK: Missing track header')
  118. error.check((cyl, head) not in edsk.to_track,
  119. 'EDSK: Track specified twice')
  120. while True:
  121. track = EDSKTrack()
  122. t = bytes()
  123. # Post-index gap
  124. t += mfm.encode(bytes([track.gapbyte] * track.gap_4a))
  125. # IAM
  126. t += mfm.encode(bytes(track.gap_presync))
  127. t += mfm.iam_sync_bytes
  128. t += mfm.encode(bytes([mfm.IBM_MFM.IAM]))
  129. t += mfm.encode(bytes([track.gapbyte] * track.gap_1))
  130. secs = dat[o+24:o+24+8*nsecs]
  131. data_pos = o + 256 # skip track header and sector-info table
  132. while secs:
  133. c, h, r, n, stat1, stat2, actual_length = struct.unpack(
  134. '<6BH', secs[:8])
  135. secs = secs[8:]
  136. size = 128 << n
  137. weak = []
  138. if size != actual_length:
  139. error.check(actual_length != 0
  140. and actual_length % size == 0,
  141. 'EDSK: Weird sector size (GAP protection?)')
  142. weak = cls().find_weak_ranges(
  143. dat[data_pos:data_pos+actual_length], size)
  144. # Update CRCs according to status flags
  145. icrc, dcrc = 0, 0
  146. if stat1 & 0x20:
  147. if stat2 & 0x20:
  148. dcrc = 0xffff
  149. else:
  150. icrc = 0xffff
  151. stat1 &= ~0x20
  152. stat2 &= ~0x20
  153. # Update address marks according to status flags
  154. imark, dmark = mfm.IBM_MFM.IDAM, mfm.IBM_MFM.DAM
  155. if stat2 & 0x40:
  156. dmark = mfm.IBM_MFM.DDAM
  157. if stat2 & 0x01:
  158. dmark = 0
  159. elif stat1 & 0x01:
  160. imark = 0
  161. stat1 &= ~0x01
  162. stat2 &= ~0x41
  163. error.check(stat1 == 0 and stat2 == 0,
  164. 'EDSK: Mangled sector (copy protection?)')
  165. sec_data = dat[data_pos:data_pos+size]
  166. data_pos += actual_length
  167. # IDAM
  168. t += mfm.encode(bytes(track.gap_presync))
  169. t += mfm.sync_bytes
  170. idam = bytes([0xa1, 0xa1, 0xa1, mfm.IBM_MFM.IDAM,
  171. c, h, r, n])
  172. idam += struct.pack('>H', mfm.crc16.new(idam).crcValue)
  173. t += mfm.encode(idam[3:])
  174. t += mfm.encode(bytes([track.gapbyte] * track.gap_2))
  175. # DAM
  176. t += mfm.encode(bytes(track.gap_presync))
  177. t += mfm.sync_bytes
  178. track.weak += [((s+len(t)//2+4)*16, n*16) for s,n in weak]
  179. dam = bytes([0xa1, 0xa1, 0xa1, dmark]) + sec_data
  180. dam += struct.pack('>H', mfm.crc16.new(dam).crcValue^dcrc)
  181. t += mfm.encode(dam[3:])
  182. t += mfm.encode(bytes([track.gapbyte] * gap_3))
  183. # Some EDSK images have bogus GAP3 values. If the track is too
  184. # long to comfortably fit in 300rpm at double density, shrink
  185. # GAP3 as far as necessary.
  186. tracklen = int((track.time_per_rev / track.clock) / 16)
  187. overhang = int(len(t)//2 - tracklen*0.99)
  188. if overhang <= 0:
  189. break
  190. new_gap_3 = gap_3 - math.ceil(overhang / nsecs)
  191. error.check(new_gap_3 >= 0,
  192. 'EDSK: Track %d.%d is too long '
  193. '(%d bits @ GAP3=%d; %d bits @ GAP3=0)'
  194. % (cyl, head, len(t)*8, gap_3,
  195. (len(t)//2-gap_3*nsecs)*16))
  196. gap_3 = new_gap_3
  197. # Pre-index gap
  198. track.verify_len = len(t)*8
  199. gap = tracklen - len(t)//2
  200. t += mfm.encode(bytes([track.gapbyte] * gap))
  201. track.bits = bitarray(endian='big')
  202. track.bits.frombytes(mfm.mfm_encode(t))
  203. edsk.to_track[cyl,head] = track
  204. o += tsize
  205. return edsk
  206. def get_track(self, cyl, side):
  207. if (cyl,side) not in self.to_track:
  208. return None
  209. return self.to_track[cyl,side].raw_track()
  210. # Local variables:
  211. # python-indent: 4
  212. # End: