amigados.py 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198
  1. # greaseweazle/codec/amiga/amigados.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 struct
  8. import itertools as it
  9. from bitarray import bitarray
  10. from greaseweazle.track import MasterTrack, RawTrack
  11. default_revs = 1.1
  12. sync_bytes = b'\x44\x89\x44\x89'
  13. sync = bitarray(endian='big')
  14. sync.frombytes(sync_bytes)
  15. bad_sector = b'-=[BAD SECTOR]=-' * 32
  16. class AmigaDOS:
  17. time_per_rev = 0.2
  18. def __init__(self, cyl, head):
  19. self.tracknr = cyl*2 + head
  20. self.sector = [None] * self.nsec
  21. self.map = [None] * self.nsec
  22. def summary_string(self):
  23. nsec, nbad = self.nsec, self.nr_missing()
  24. s = "AmigaDOS (%d/%d sectors)" % (nsec - nbad, nsec)
  25. if nbad != 0:
  26. s += " - %d sectors missing" % nbad
  27. return s
  28. # private
  29. def exists(self, sec_id, togo):
  30. return ((self.sector[sec_id] is not None)
  31. or (self.map[self.nsec-togo] is not None))
  32. # private
  33. def add(self, sec_id, togo, label, data):
  34. assert not self.exists(sec_id, togo)
  35. self.sector[sec_id] = label, data
  36. self.map[self.nsec-togo] = sec_id
  37. def has_sec(self, sec_id):
  38. return self.sector[sec_id] is not None
  39. def nr_missing(self):
  40. return len([sec for sec in self.sector if sec is None])
  41. def get_adf_track(self):
  42. tdat = bytearray()
  43. for sec in self.sector:
  44. tdat += sec[1] if sec is not None else bad_sector
  45. return tdat
  46. def set_adf_track(self, tdat):
  47. totsize = self.nsec * 512
  48. if len(tdat) < totsize:
  49. tdat += bytes(totsize - len(tdat))
  50. self.map = list(range(self.nsec))
  51. for sec in self.map:
  52. self.sector[sec] = bytes(16), tdat[sec*512:(sec+1)*512]
  53. return totsize
  54. def flux(self, *args, **kwargs):
  55. return self.raw_track().flux(*args, **kwargs)
  56. def decode_raw(self, track):
  57. raw = RawTrack(clock = self.clock, data = track)
  58. bits, _ = raw.get_all_data()
  59. for offs in bits.itersearch(sync):
  60. if self.nr_missing() == 0:
  61. break
  62. sec = bits[offs:offs+544*16].tobytes()
  63. if len(sec) != 1088:
  64. continue
  65. header = decode(sec[4:12])
  66. format, track, sec_id, togo = tuple(header)
  67. if format != 0xff or track != self.tracknr \
  68. or not(sec_id < self.nsec and 0 < togo <= self.nsec) \
  69. or self.exists(sec_id, togo):
  70. continue
  71. label = decode(sec[12:44])
  72. hsum, = struct.unpack('>I', decode(sec[44:52]))
  73. if hsum != checksum(header + label):
  74. continue
  75. dsum, = struct.unpack('>I', decode(sec[52:60]))
  76. data = decode(sec[60:1084])
  77. gap = decode(sec[1084:1088])
  78. if dsum != checksum(data):
  79. continue;
  80. self.add(sec_id, togo, label, data)
  81. def raw_track(self):
  82. # List of sector IDs missing from the sector map:
  83. missing = iter([x for x in range(self.nsec) if not x in self.map])
  84. # Sector map with the missing entries filled in:
  85. full_map = [next(missing) if x is None else x for x in self.map]
  86. # Post-index track gap.
  87. t = encode(bytes(128 * (self.nsec//11)))
  88. for nr, sec_id in zip(range(self.nsec), full_map):
  89. sector = self.sector[sec_id]
  90. label, data = (bytes(16), bad_sector) if sector is None else sector
  91. header = bytes([0xff, self.tracknr, sec_id, self.nsec-nr])
  92. t += sync_bytes
  93. t += encode(header)
  94. t += encode(label)
  95. t += encode(struct.pack('>I', checksum(header + label)))
  96. t += encode(struct.pack('>I', checksum(data)))
  97. t += encode(data)
  98. t += encode(bytes(2))
  99. # Add the pre-index gap.
  100. tlen = (int((self.time_per_rev / self.clock)) + 31) & ~31
  101. t += bytes(tlen//8-len(t))
  102. track = MasterTrack(
  103. bits = mfm_encode(t),
  104. time_per_rev = 0.2)
  105. track.verify = self
  106. track.verify_revs = default_revs
  107. return track
  108. def verify_track(self, flux):
  109. cyl = self.tracknr // 2
  110. head = self.tracknr & 1
  111. readback_track = self.decode_track(cyl, head, flux)
  112. return (readback_track.nr_missing() == 0
  113. and self.sector == readback_track.sector)
  114. @classmethod
  115. def decode_track(cls, cyl, head, track):
  116. ados = cls(cyl, head)
  117. ados.decode_raw(track)
  118. return ados
  119. class AmigaDOS_DD(AmigaDOS):
  120. nsec = 11
  121. clock = 14/7093790
  122. class AmigaDOS_HD(AmigaDOS):
  123. nsec = 22
  124. clock = AmigaDOS_DD.clock / 2
  125. def mfm_encode(dat):
  126. y = 0
  127. out = bytearray()
  128. for x in dat:
  129. y = (y<<8) | x
  130. if (x & 0xaa) == 0:
  131. y |= ~((y>>1)|(y<<1)) & 0xaaaa
  132. y &= 255
  133. out.append(y)
  134. return bytes(out)
  135. def encode(dat):
  136. return bytes(it.chain(map(lambda x: (x >> 1) & 0x55, dat),
  137. map(lambda x: x & 0x55, dat)))
  138. def decode(dat):
  139. length = len(dat)//2
  140. return bytes(map(lambda x, y: (x << 1 & 0xaa) | (y & 0x55),
  141. it.islice(dat, 0, length),
  142. it.islice(dat, length, None)))
  143. def checksum(dat):
  144. csum = 0
  145. for i in range(0, len(dat), 4):
  146. csum ^= struct.unpack('>I', dat[i:i+4])[0]
  147. return (csum ^ (csum>>1)) & 0x55555555
  148. # Local variables:
  149. # python-indent: 4
  150. # End: