ipf.py 10 KB

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