edsk.py 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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. tsizes = [track_sz] * (ncyls * nsides)
  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 = mfm.sec_sz(n)
  137. weak = []
  138. if not extended:
  139. actual_length = mfm.sec_sz(sec_sz)
  140. elif size != actual_length:
  141. error.check(actual_length != 0
  142. and actual_length % size == 0,
  143. 'EDSK: Weird sector size (GAP protection?)')
  144. weak = cls().find_weak_ranges(
  145. dat[data_pos:data_pos+actual_length], size)
  146. # Update CRCs according to status flags
  147. icrc, dcrc = 0, 0
  148. if stat1 & 0x20:
  149. if stat2 & 0x20:
  150. dcrc = 0xffff
  151. else:
  152. icrc = 0xffff
  153. stat1 &= ~0x20
  154. stat2 &= ~0x20
  155. # Update address marks according to status flags
  156. imark, dmark = mfm.IBM_MFM.IDAM, mfm.IBM_MFM.DAM
  157. if stat2 & 0x40:
  158. dmark = mfm.IBM_MFM.DDAM
  159. if stat2 & 0x01:
  160. dmark = 0
  161. elif stat1 & 0x01:
  162. imark = 0
  163. stat1 &= ~0x01
  164. stat2 &= ~0x41
  165. error.check(stat1 == 0 and stat2 == 0,
  166. 'EDSK: Mangled sector (copy protection?)')
  167. sec_data = dat[data_pos:data_pos+size]
  168. data_pos += actual_length
  169. # IDAM
  170. t += mfm.encode(bytes(track.gap_presync))
  171. t += mfm.sync_bytes
  172. am = bytes([0xa1, 0xa1, 0xa1, imark, c, h, r, n])
  173. am += struct.pack('>H', mfm.crc16.new(am).crcValue^icrc)
  174. t += mfm.encode(am[3:])
  175. t += mfm.encode(bytes([track.gapbyte] * track.gap_2))
  176. # DAM
  177. t += mfm.encode(bytes(track.gap_presync))
  178. t += mfm.sync_bytes
  179. track.weak += [((s+len(t)//2+4)*16, n*16) for s,n in weak]
  180. am = bytes([0xa1, 0xa1, 0xa1, dmark]) + sec_data
  181. am += struct.pack('>H', mfm.crc16.new(am).crcValue^dcrc)
  182. t += mfm.encode(am[3:])
  183. t += mfm.encode(bytes([track.gapbyte] * gap_3))
  184. # Some EDSK images have bogus GAP3 values. If the track is too
  185. # long to comfortably fit in 300rpm at double density, shrink
  186. # GAP3 as far as necessary.
  187. tracklen = int((track.time_per_rev / track.clock) / 16)
  188. overhang = int(len(t)//2 - tracklen*0.99)
  189. if overhang <= 0:
  190. break
  191. new_gap_3 = gap_3 - math.ceil(overhang / nsecs)
  192. error.check(new_gap_3 >= 0,
  193. 'EDSK: Track %d.%d is too long '
  194. '(%d bits @ GAP3=%d; %d bits @ GAP3=0)'
  195. % (cyl, head, len(t)*8, gap_3,
  196. (len(t)//2-gap_3*nsecs)*16))
  197. #print('EDSK: GAP3 reduced (%d -> %d)' % (gap_3, new_gap_3))
  198. gap_3 = new_gap_3
  199. # Pre-index gap
  200. track.verify_len = len(t)*8
  201. gap = tracklen - len(t)//2
  202. t += mfm.encode(bytes([track.gapbyte] * gap))
  203. track.bits = bitarray(endian='big')
  204. track.bits.frombytes(mfm.mfm_encode(t))
  205. edsk.to_track[cyl,head] = track
  206. o += tsize
  207. return edsk
  208. def get_track(self, cyl, side):
  209. if (cyl,side) not in self.to_track:
  210. return None
  211. return self.to_track[cyl,side].raw_track()
  212. # Local variables:
  213. # python-indent: 4
  214. # End: