ipf.py 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. # greaseweazle/image/ipf.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 os, sys
  8. import platform
  9. import ctypes as ct
  10. from bitarray import bitarray
  11. from greaseweazle.flux import Flux
  12. class CapsDateTimeExt(ct.Structure):
  13. _pack_ = 1
  14. _fields_ = [
  15. ('year', ct.c_uint),
  16. ('month', ct.c_uint),
  17. ('day', ct.c_uint),
  18. ('hour', ct.c_uint),
  19. ('min', ct.c_uint),
  20. ('sec', ct.c_uint),
  21. ('tick', ct.c_uint)]
  22. class CapsImageInfo(ct.Structure):
  23. platform_name = [
  24. "N/A", "Amiga", "Atari ST", "IBM PC", "Amstrad CPC",
  25. "Spectrum", "Sam Coupe", "Archimedes", "C64", "Atari (8-bit)" ]
  26. _pack_ = 1
  27. _fields_ = [
  28. ('type', ct.c_uint), # image type
  29. ('release', ct.c_uint), # release ID
  30. ('revision', ct.c_uint), # release revision ID
  31. ('mincylinder', ct.c_uint), # lowest cylinder number
  32. ('maxcylinder', ct.c_uint), # highest cylinder number
  33. ('minhead', ct.c_uint), # lowest head number
  34. ('maxhead', ct.c_uint), # highest head number
  35. ('crdt', CapsDateTimeExt), # image creation date.time
  36. ('platform', ct.c_uint * 4)] # intended platform(s)
  37. class CapsTrackInfoT2(ct.Structure):
  38. _pack_ = 1
  39. _fields_ = [
  40. ('type', ct.c_uint), # track type
  41. ('cylinder', ct.c_uint), # cylinder#
  42. ('head', ct.c_uint), # head#
  43. ('sectorcnt', ct.c_uint), # available sectors
  44. ('sectorsize', ct.c_uint), # sector size, unused
  45. ('trackbuf', ct.POINTER(ct.c_ubyte)), # track buffer memory
  46. ('tracklen', ct.c_uint), # track buffer memory length
  47. ('timelen', ct.c_uint), # timing buffer length
  48. ('timebuf', ct.POINTER(ct.c_uint)), # timing buffer
  49. ('overlap', ct.c_int), # overlap position
  50. ('startbit', ct.c_uint), # start position of the decoding
  51. ('wseed', ct.c_uint), # weak bit generator data
  52. ('weakcnt', ct.c_uint)] # number of weak data areas
  53. class CapsSectorInfo(ct.Structure):
  54. _pack_ = 1
  55. _fields_ = [
  56. ('descdatasize', ct.c_uint), # data size in bits from IPF descriptor
  57. ('descgapsize', ct.c_uint), # gap size in bits from IPF descriptor
  58. ('datasize', ct.c_uint), # data size in bits from decoder
  59. ('gapsize', ct.c_uint), # gap size in bits from decoder
  60. ('datastart', ct.c_uint), # data start pos in bits from decoder
  61. ('gapstart', ct.c_uint), # gap start pos in bits from decoder
  62. ('gapsizews0', ct.c_uint), # gap size before write splice
  63. ('gapsizews1', ct.c_uint), # gap size after write splice
  64. ('gapws0mode', ct.c_uint), # gap size mode before write splice
  65. ('gapws1mode', ct.c_uint), # gap size mode after write splice
  66. ('celltype', ct.c_uint), # bitcell type
  67. ('enctype', ct.c_uint)] # encoder type
  68. class CapsDataInfo(ct.Structure):
  69. _pack_ = 1
  70. _fields_ = [
  71. ('type', ct.c_uint), # data type
  72. ('start', ct.c_uint), # start position
  73. ('size', ct.c_uint)] # size in bits
  74. class DI_LOCK:
  75. INDEX = 1<<0
  76. ALIGN = 1<<1
  77. DENVAR = 1<<2
  78. DENAUTO = 1<<3
  79. DENNOISE = 1<<4
  80. NOISE = 1<<5
  81. NOISEREV = 1<<6
  82. MEMREF = 1<<7
  83. UPDATEFD = 1<<8
  84. TYPE = 1<<9
  85. DENALT = 1<<10
  86. OVLBIT = 1<<11
  87. TRKBIT = 1<<12
  88. NOUPDATE = 1<<13
  89. SETWSEED = 1<<14
  90. def_flags = (DENVAR | UPDATEFD | TYPE | OVLBIT | TRKBIT)
  91. class IPF:
  92. def __init__(self, start_cyl, nr_sides):
  93. self.lib = get_libcaps()
  94. self.start_cyl = start_cyl
  95. self.nr_sides = nr_sides
  96. def __del__(self):
  97. try:
  98. self.lib.CAPSUnlockAllTracks(self.iid)
  99. self.lib.CAPSUnlockImage(self.iid)
  100. self.lib.CAPSRemImage(self.iid)
  101. del(self.iid)
  102. except AttributeError:
  103. pass
  104. def __str__(self):
  105. pi = self.pi
  106. s = "IPF Image File:"
  107. s += "\n SPS ID: %04d (rev %d)" % (pi.release, pi.revision)
  108. s += "\n Platform: "
  109. nr_platforms = 0
  110. for p in pi.platform:
  111. if p == 0 and nr_platforms != 0:
  112. break
  113. if nr_platforms > 0:
  114. s += ", "
  115. s += pi.platform_name[p]
  116. nr_platforms += 1
  117. s += ("\n Created: %d/%d/%d %02d:%02d:%02d"
  118. % (pi.crdt.year, pi.crdt.month, pi.crdt.day,
  119. pi.crdt.hour, pi.crdt.min, pi.crdt.sec))
  120. s += ("\n Cyls: %d-%d Heads: %d-%d"
  121. % (pi.mincylinder, pi.maxcylinder, pi.minhead, pi.maxhead))
  122. return s
  123. @classmethod
  124. def from_filename(cls, name):
  125. ipf = cls(0, 0)
  126. ipf.iid = ipf.lib.CAPSAddImage()
  127. assert ipf.iid >= 0, "Could not create IPF image container"
  128. cname = ct.c_char_p(name.encode())
  129. res = ipf.lib.CAPSLockImage(ipf.iid, cname)
  130. assert res == 0, "Could not open IPF image '%s'" % name
  131. res = ipf.lib.CAPSLoadImage(ipf.iid, DI_LOCK.def_flags)
  132. assert res == 0, "Could not load IPF image '%s'" % name
  133. ipf.pi = CapsImageInfo()
  134. res = ipf.lib.CAPSGetImageInfo(ct.byref(ipf.pi), ipf.iid)
  135. assert res == 0
  136. print(ipf)
  137. return ipf
  138. def get_track(self, cyl, head, writeout=False):
  139. pi = self.pi
  140. if head < pi.minhead or head > pi.maxhead:
  141. return None
  142. if cyl < pi.mincylinder or cyl > pi.maxcylinder:
  143. return None
  144. ti = CapsTrackInfoT2(2)
  145. res = self.lib.CAPSLockTrack(ct.byref(ti), self.iid,
  146. cyl, head, DI_LOCK.def_flags)
  147. assert res == 0
  148. if not ti.trackbuf:
  149. return None # unformatted/empty
  150. carray_type = ct.c_ubyte * ((ti.tracklen+7)//8)
  151. carray = carray_type.from_address(
  152. ct.addressof(ti.trackbuf.contents))
  153. trackbuf = bitarray(endian='big')
  154. trackbuf.frombytes(bytes(carray))
  155. trackbuf = trackbuf[:ti.tracklen]
  156. # Write splice is at trackbuf[ti.overlap]. Index is at trackbuf[0].
  157. #for i in range(ti.sectorcnt):
  158. # si = CapsSectorInfo()
  159. # res = self.lib.CAPSGetInfo(ct.byref(si), self.iid,
  160. # cyl, head, 1, i)
  161. # assert res == 0
  162. # # Data is at trackbuf[si.datastart:si.datastart + si.datasize]
  163. #for i in range(ti.weakcnt):
  164. # wi = CapsDataInfo()
  165. # res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid,
  166. # cyl, head, 2, i)
  167. # assert res == 0
  168. # # Weak data at trackbuf[wi.start:wi.start + wi.size]
  169. assert ti.weakcnt == 0, "Can't yet handle weak data"
  170. # We don't really have access to the bitrate. It depends on RPM.
  171. # So we assume a rotation rate of 300 RPM (5 rev/sec).
  172. bitrate = ti.tracklen * 5
  173. timebuf = None
  174. if ti.timebuf:
  175. carray_type = ct.c_uint * ti.timelen
  176. carray = carray_type.from_address(
  177. ct.addressof(ti.timebuf.contents))
  178. # Unpack the per-byte timing info into per-bitcell
  179. timebuf = []
  180. for i in carray:
  181. for j in range(8):
  182. timebuf.append(i)
  183. # Pad the timing info with normal cell lengths as necessary
  184. for j in range(len(carray)*8, ti.tracklen):
  185. timebuf.append(1000)
  186. # Clip the timing info, if necessary.
  187. timebuf = timebuf[:ti.tracklen]
  188. # TODO: Place overlap (write splice) at the correct position.
  189. if ti.overlap != 0:
  190. trackbuf = trackbuf[ti.overlap:] + trackbuf[:ti.overlap]
  191. if timebuf:
  192. timebuf = timebuf[ti.overlap:] + timebuf[:ti.overlap]
  193. return Flux.from_bitarray(trackbuf, bitrate, timebuf)
  194. # Open and initialise the CAPS library.
  195. def open_libcaps():
  196. # Get the OS-dependent list of valid CAPS library names.
  197. _names = []
  198. if platform.system() == "Linux":
  199. _names = [ "libcapsimage.so.5", "libcapsimage.so.5.1",
  200. "libcapsimage.so.4", "libcapsimage.so.4.2",
  201. "libcapsimage.so" ]
  202. elif platform.system() == "Darwin":
  203. _names = [ "CAPSImage.framework/CAPSImage" ]
  204. elif platform.system() == "Windows":
  205. _names = [ "CAPSImg_x64.dll", "CAPSImg.dll" ]
  206. # Get the absolute path to the root Greaseweazle folder.
  207. path = os.path.dirname(os.path.abspath(__file__))
  208. for _ in range(3):
  209. path = os.path.join(path, os.pardir)
  210. path = os.path.normpath(path)
  211. # Create a search list of both relative and absolute library names.
  212. names = []
  213. for name in _names:
  214. names.append(name)
  215. names.append(os.path.join(path, name))
  216. # Walk the search list, trying to open the CAPS library.
  217. for name in names:
  218. try:
  219. lib = ct.cdll.LoadLibrary(name)
  220. break
  221. except:
  222. pass
  223. if "lib" not in locals():
  224. print("""\
  225. ** Could not find SPS/CAPS IPF decode library
  226. For installation instructions please read the wiki:
  227. <https://github.com/keirf/Greaseweazle/wiki/IPF-Images>""")
  228. sys.exit(1)
  229. # We have opened the library. Now initialise it.
  230. res = lib.CAPSInit()
  231. assert res == 0, "Failure initialising %s" % libname
  232. return lib
  233. # Get a reference to the CAPS library. Open it if necessary.
  234. def get_libcaps():
  235. global libcaps
  236. if not 'libcaps' in globals():
  237. libcaps = open_libcaps()
  238. return libcaps
  239. # Local variables:
  240. # python-indent: 4
  241. # End: