hfe.py 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135
  1. # greaseweazle/image/hfe.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. from greaseweazle import error
  9. from greaseweazle.track import MasterTrack, RawTrack
  10. from bitarray import bitarray
  11. from .image import Image
  12. class HFE(Image):
  13. def __init__(self):
  14. self.bitrate = 250 # XXX real bitrate?
  15. # Each track is (bitlen, rawbytes).
  16. # rawbytes is a bytes() object in little-endian bit order.
  17. self.to_track = dict()
  18. @classmethod
  19. def from_file(cls, name):
  20. with open(name, "rb") as f:
  21. dat = f.read()
  22. (sig, f_rev, n_cyl, n_side, t_enc, bitrate,
  23. _, _, _, tlut_base) = struct.unpack("<8s4B2H2BH", dat[:20])
  24. error.check(sig != b"HXCHFEV3", "HFEv3 is not supported")
  25. error.check(sig == b"HXCPICFE" and f_rev <= 1, "Not a valid HFE file")
  26. error.check(0 < n_cyl, "HFE: Invalid #cyls")
  27. error.check(0 < n_side < 3, "HFE: Invalid #sides")
  28. error.check(bitrate != 0, "HFE: Invalid bitrate")
  29. hfe = cls()
  30. hfe.bitrate = bitrate
  31. tlut = dat[tlut_base*512:tlut_base*512+n_cyl*4]
  32. for cyl in range(n_cyl):
  33. for side in range(n_side):
  34. offset, length = struct.unpack("<2H", tlut[cyl*4:(cyl+1)*4])
  35. todo = length // 2
  36. tdat = bytes()
  37. while todo:
  38. d_off = offset*512 + side*256
  39. d_nr = 256 if todo > 256 else todo
  40. tdat += dat[d_off:d_off+d_nr]
  41. todo -= d_nr
  42. offset += 1
  43. hfe.to_track[cyl,side] = (len(tdat)*8, tdat)
  44. return hfe
  45. def get_track(self, cyl, side):
  46. if (cyl,side) not in self.to_track:
  47. return None
  48. bitlen, rawbytes = self.to_track[cyl,side]
  49. tdat = bitarray(endian='little')
  50. tdat.frombytes(rawbytes)
  51. track = MasterTrack(
  52. bits = tdat[:bitlen],
  53. time_per_rev = bitlen / (2000*self.bitrate))
  54. return track
  55. def emit_track(self, cyl, side, track):
  56. flux = track.flux()
  57. flux.cue_at_index()
  58. raw = RawTrack(clock = 5e-4 / self.bitrate, data = flux)
  59. bits, _ = raw.get_revolution(0)
  60. bits.bytereverse()
  61. self.to_track[cyl,side] = (len(bits), bits.tobytes())
  62. def get_image(self):
  63. n_side = 1
  64. n_cyl = max(self.to_track.keys(), default=(0), key=lambda x:x[0])[0]
  65. n_cyl += 1
  66. # We dynamically build the Track-LUT and -Data arrays.
  67. tlut = bytearray()
  68. tdat = bytearray()
  69. # Stuff real data into the image.
  70. for i in range(n_cyl):
  71. s0 = self.to_track[i,0] if (i,0) in self.to_track else None
  72. s1 = self.to_track[i,1] if (i,1) in self.to_track else None
  73. if s0 is None and s1 is None:
  74. # Dummy data for empty cylinders. Assumes 300RPM.
  75. nr_bytes = 100 * self.bitrate
  76. tlut += struct.pack("<2H", len(tdat)//512 + 2, nr_bytes)
  77. tdat += bytes([0x88] * (nr_bytes+0x1ff & ~0x1ff))
  78. else:
  79. # At least one side of this cylinder is populated.
  80. if s1 is not None:
  81. n_side = 2
  82. bc = [s0 if s0 is not None else (0,bytes()),
  83. s1 if s1 is not None else (0,bytes())]
  84. nr_bytes = max(len(t[1]) for t in bc)
  85. nr_blocks = (nr_bytes + 0xff) // 0x100
  86. tlut += struct.pack("<2H", len(tdat)//512 + 2, 2 * nr_bytes)
  87. for b in range(nr_blocks):
  88. for t in bc:
  89. slice = t[1][b*256:(b+1)*256]
  90. tdat += slice + bytes([0x88] * (256 - len(slice)))
  91. # Construct the image header.
  92. header = struct.pack("<8s4B2H2BH",
  93. b"HXCPICFE",
  94. 0,
  95. n_cyl,
  96. n_side,
  97. 0xff, # unknown encoding
  98. self.bitrate,
  99. 0, # rpm (unused)
  100. 0xff, # unknown interface
  101. 1, # rsvd
  102. 1) # track list offset
  103. # Pad the header and TLUT to 512-byte blocks.
  104. header += bytes([0xff] * (0x200 - len(header)))
  105. tlut += bytes([0xff] * (0x200 - len(tlut)))
  106. return header + tlut + tdat
  107. # Local variables:
  108. # python-indent: 4
  109. # End: