ipf.py 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  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. from .image import Image
  14. class CapsDateTimeExt(ct.Structure):
  15. _pack_ = 1
  16. _fields_ = [
  17. ('year', ct.c_uint),
  18. ('month', ct.c_uint),
  19. ('day', ct.c_uint),
  20. ('hour', ct.c_uint),
  21. ('min', ct.c_uint),
  22. ('sec', ct.c_uint),
  23. ('tick', ct.c_uint)]
  24. class CapsImageInfo(ct.Structure):
  25. platform_name = [
  26. "N/A", "Amiga", "Atari ST", "IBM PC", "Amstrad CPC",
  27. "Spectrum", "Sam Coupe", "Archimedes", "C64", "Atari (8-bit)" ]
  28. _pack_ = 1
  29. _fields_ = [
  30. ('type', ct.c_uint), # image type
  31. ('release', ct.c_uint), # release ID
  32. ('revision', ct.c_uint), # release revision ID
  33. ('mincylinder', ct.c_uint), # lowest cylinder number
  34. ('maxcylinder', ct.c_uint), # highest cylinder number
  35. ('minhead', ct.c_uint), # lowest head number
  36. ('maxhead', ct.c_uint), # highest head number
  37. ('crdt', CapsDateTimeExt), # image creation date.time
  38. ('platform', ct.c_uint * 4)] # intended platform(s)
  39. class CapsTrackInfoT2(ct.Structure):
  40. _pack_ = 1
  41. _fields_ = [
  42. ('type', ct.c_uint), # track type
  43. ('cylinder', ct.c_uint), # cylinder#
  44. ('head', ct.c_uint), # head#
  45. ('sectorcnt', ct.c_uint), # available sectors
  46. ('sectorsize', ct.c_uint), # sector size, unused
  47. ('trackbuf', ct.POINTER(ct.c_ubyte)), # track buffer memory
  48. ('tracklen', ct.c_uint), # track buffer memory length
  49. ('timelen', ct.c_uint), # timing buffer length
  50. ('timebuf', ct.POINTER(ct.c_uint)), # timing buffer
  51. ('overlap', ct.c_int), # overlap position
  52. ('startbit', ct.c_uint), # start position of the decoding
  53. ('wseed', ct.c_uint), # weak bit generator data
  54. ('weakcnt', ct.c_uint)] # number of weak data areas
  55. class CapsSectorInfo(ct.Structure):
  56. _pack_ = 1
  57. _fields_ = [
  58. ('descdatasize', ct.c_uint), # data size in bits from IPF descriptor
  59. ('descgapsize', ct.c_uint), # gap size in bits from IPF descriptor
  60. ('datasize', ct.c_uint), # data size in bits from decoder
  61. ('gapsize', ct.c_uint), # gap size in bits from decoder
  62. ('datastart', ct.c_uint), # data start pos in bits from decoder
  63. ('gapstart', ct.c_uint), # gap start pos in bits from decoder
  64. ('gapsizews0', ct.c_uint), # gap size before write splice
  65. ('gapsizews1', ct.c_uint), # gap size after write splice
  66. ('gapws0mode', ct.c_uint), # gap size mode before write splice
  67. ('gapws1mode', ct.c_uint), # gap size mode after write splice
  68. ('celltype', ct.c_uint), # bitcell type
  69. ('enctype', ct.c_uint)] # encoder type
  70. class CapsDataInfo(ct.Structure):
  71. _pack_ = 1
  72. _fields_ = [
  73. ('type', ct.c_uint), # data type
  74. ('start', ct.c_uint), # start position
  75. ('size', ct.c_uint)] # size in bits
  76. class DI_LOCK:
  77. INDEX = 1<<0
  78. ALIGN = 1<<1
  79. DENVAR = 1<<2
  80. DENAUTO = 1<<3
  81. DENNOISE = 1<<4
  82. NOISE = 1<<5
  83. NOISEREV = 1<<6
  84. MEMREF = 1<<7
  85. UPDATEFD = 1<<8
  86. TYPE = 1<<9
  87. DENALT = 1<<10
  88. OVLBIT = 1<<11
  89. TRKBIT = 1<<12
  90. NOUPDATE = 1<<13
  91. SETWSEED = 1<<14
  92. def_flags = (DENVAR | UPDATEFD | TYPE | OVLBIT | TRKBIT)
  93. class IPF(Image):
  94. read_only = True
  95. def __init__(self):
  96. self.lib = get_libcaps()
  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_file(cls, name):
  126. ipf = cls()
  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):
  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: