diskstack 1.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. diskstack/__init__.py +5 -0
  2. diskstack/_vendor/__init__.py +0 -0
  3. diskstack/_vendor/greaseweazle/COPYING +24 -0
  4. diskstack/_vendor/greaseweazle/__init__.py +2 -0
  5. diskstack/_vendor/greaseweazle/codec/__init__.py +0 -0
  6. diskstack/_vendor/greaseweazle/codec/amiga/__init__.py +0 -0
  7. diskstack/_vendor/greaseweazle/codec/amiga/amigados.py +221 -0
  8. diskstack/_vendor/greaseweazle/codec/apple2/__init__.py +0 -0
  9. diskstack/_vendor/greaseweazle/codec/apple2/apple2_gcr.py +223 -0
  10. diskstack/_vendor/greaseweazle/codec/bitcell.py +117 -0
  11. diskstack/_vendor/greaseweazle/codec/codec.py +362 -0
  12. diskstack/_vendor/greaseweazle/codec/commodore/__init__.py +0 -0
  13. diskstack/_vendor/greaseweazle/codec/commodore/c64_gcr.py +229 -0
  14. diskstack/_vendor/greaseweazle/codec/datageneral/__init__.py +0 -0
  15. diskstack/_vendor/greaseweazle/codec/datageneral/datageneral.py +211 -0
  16. diskstack/_vendor/greaseweazle/codec/hp/__init__.py +0 -0
  17. diskstack/_vendor/greaseweazle/codec/hp/hp_mmfm.py +258 -0
  18. diskstack/_vendor/greaseweazle/codec/ibm/__init__.py +0 -0
  19. diskstack/_vendor/greaseweazle/codec/ibm/ibm.py +1005 -0
  20. diskstack/_vendor/greaseweazle/codec/macintosh/__init__.py +0 -0
  21. diskstack/_vendor/greaseweazle/codec/macintosh/mac_gcr.py +238 -0
  22. diskstack/_vendor/greaseweazle/codec/micropolis/__init__.py +0 -0
  23. diskstack/_vendor/greaseweazle/codec/micropolis/micropolis.py +233 -0
  24. diskstack/_vendor/greaseweazle/codec/northstar/__init__.py +0 -0
  25. diskstack/_vendor/greaseweazle/codec/northstar/northstar.py +210 -0
  26. diskstack/_vendor/greaseweazle/data/__init__.py +0 -0
  27. diskstack/_vendor/greaseweazle/data/diskdefs.cfg +81 -0
  28. diskstack/_vendor/greaseweazle/data/diskdefs_acorn.cfg +119 -0
  29. diskstack/_vendor/greaseweazle/data/diskdefs_akai.cfg +27 -0
  30. diskstack/_vendor/greaseweazle/data/diskdefs_amiga.cfg +17 -0
  31. diskstack/_vendor/greaseweazle/data/diskdefs_apple2.cfg +31 -0
  32. diskstack/_vendor/greaseweazle/data/diskdefs_apricot.cfg +25 -0
  33. diskstack/_vendor/greaseweazle/data/diskdefs_atari.cfg +33 -0
  34. diskstack/_vendor/greaseweazle/data/diskdefs_atarist.cfg +76 -0
  35. diskstack/_vendor/greaseweazle/data/diskdefs_coco.cfg +98 -0
  36. diskstack/_vendor/greaseweazle/data/diskdefs_commodore.cfg +127 -0
  37. diskstack/_vendor/greaseweazle/data/diskdefs_datageneral.cfg +8 -0
  38. diskstack/_vendor/greaseweazle/data/diskdefs_dec.cfg +37 -0
  39. diskstack/_vendor/greaseweazle/data/diskdefs_dragon.cfg +61 -0
  40. diskstack/_vendor/greaseweazle/data/diskdefs_eagle.cfg +25 -0
  41. diskstack/_vendor/greaseweazle/data/diskdefs_ensoniq.cfg +39 -0
  42. diskstack/_vendor/greaseweazle/data/diskdefs_epson.cfg +95 -0
  43. diskstack/_vendor/greaseweazle/data/diskdefs_gem.cfg +15 -0
  44. diskstack/_vendor/greaseweazle/data/diskdefs_hp.cfg +25 -0
  45. diskstack/_vendor/greaseweazle/data/diskdefs_ibm.cfg +140 -0
  46. diskstack/_vendor/greaseweazle/data/diskdefs_kaypro.cfg +58 -0
  47. diskstack/_vendor/greaseweazle/data/diskdefs_luxor.cfg +96 -0
  48. diskstack/_vendor/greaseweazle/data/diskdefs_mac.cfg +71 -0
  49. diskstack/_vendor/greaseweazle/data/diskdefs_micropolis.cfg +73 -0
  50. diskstack/_vendor/greaseweazle/data/diskdefs_mm1.cfg +69 -0
  51. diskstack/_vendor/greaseweazle/data/diskdefs_msx.cfg +45 -0
  52. diskstack/_vendor/greaseweazle/data/diskdefs_northstar.cfg +37 -0
  53. diskstack/_vendor/greaseweazle/data/diskdefs_occ1.cfg +23 -0
  54. diskstack/_vendor/greaseweazle/data/diskdefs_olivetti.cfg +18 -0
  55. diskstack/_vendor/greaseweazle/data/diskdefs_pc98.cfg +69 -0
  56. diskstack/_vendor/greaseweazle/data/diskdefs_raw.cfg +25 -0
  57. diskstack/_vendor/greaseweazle/data/diskdefs_rm.cfg +98 -0
  58. diskstack/_vendor/greaseweazle/data/diskdefs_sci.cfg +15 -0
  59. diskstack/_vendor/greaseweazle/data/diskdefs_sega.cfg +13 -0
  60. diskstack/_vendor/greaseweazle/data/diskdefs_sharp.cfg +13 -0
  61. diskstack/_vendor/greaseweazle/data/diskdefs_thomson.cfg +78 -0
  62. diskstack/_vendor/greaseweazle/data/diskdefs_tsc.cfg +81 -0
  63. diskstack/_vendor/greaseweazle/data/diskdefs_xerox.cfg +59 -0
  64. diskstack/_vendor/greaseweazle/data/diskdefs_zx.cfg +245 -0
  65. diskstack/_vendor/greaseweazle/error.py +19 -0
  66. diskstack/_vendor/greaseweazle/flux.py +315 -0
  67. diskstack/_vendor/greaseweazle/image/__init__.py +0 -0
  68. diskstack/_vendor/greaseweazle/image/a2r.py +133 -0
  69. diskstack/_vendor/greaseweazle/image/acorn.py +28 -0
  70. diskstack/_vendor/greaseweazle/image/adf.py +15 -0
  71. diskstack/_vendor/greaseweazle/image/apple2.py +18 -0
  72. diskstack/_vendor/greaseweazle/image/apridisk.py +107 -0
  73. diskstack/_vendor/greaseweazle/image/caps.py +464 -0
  74. diskstack/_vendor/greaseweazle/image/d64.py +51 -0
  75. diskstack/_vendor/greaseweazle/image/d81.py +28 -0
  76. diskstack/_vendor/greaseweazle/image/d88.py +211 -0
  77. diskstack/_vendor/greaseweazle/image/dcp.py +45 -0
  78. diskstack/_vendor/greaseweazle/image/dim.py +53 -0
  79. diskstack/_vendor/greaseweazle/image/dmk.py +166 -0
  80. diskstack/_vendor/greaseweazle/image/dsk.py +31 -0
  81. diskstack/_vendor/greaseweazle/image/edsk.py +591 -0
  82. diskstack/_vendor/greaseweazle/image/fd.py +23 -0
  83. diskstack/_vendor/greaseweazle/image/fdi.py +41 -0
  84. diskstack/_vendor/greaseweazle/image/hdm.py +15 -0
  85. diskstack/_vendor/greaseweazle/image/hfe.py +742 -0
  86. diskstack/_vendor/greaseweazle/image/image.py +130 -0
  87. diskstack/_vendor/greaseweazle/image/imd.py +201 -0
  88. diskstack/_vendor/greaseweazle/image/img.py +111 -0
  89. diskstack/_vendor/greaseweazle/image/kryoflux.py +304 -0
  90. diskstack/_vendor/greaseweazle/image/mgt.py +15 -0
  91. diskstack/_vendor/greaseweazle/image/msa.py +153 -0
  92. diskstack/_vendor/greaseweazle/image/nfd.py +113 -0
  93. diskstack/_vendor/greaseweazle/image/nsi.py +47 -0
  94. diskstack/_vendor/greaseweazle/image/scp.py +471 -0
  95. diskstack/_vendor/greaseweazle/image/sf7.py +15 -0
  96. diskstack/_vendor/greaseweazle/image/sharp2d.py +15 -0
  97. diskstack/_vendor/greaseweazle/image/td0.py +141 -0
  98. diskstack/_vendor/greaseweazle/image/xdf.py +15 -0
  99. diskstack/_vendor/greaseweazle/optimised/__init__.py +22 -0
  100. diskstack/_vendor/greaseweazle/tools/__init__.py +0 -0
  101. diskstack/_vendor/greaseweazle/tools/util.py +133 -0
  102. diskstack/_vendor/greaseweazle/track.py +494 -0
  103. diskstack/cache.py +210 -0
  104. diskstack/candidates.py +396 -0
  105. diskstack/cli.py +348 -0
  106. diskstack/errors.py +5 -0
  107. diskstack/filler.py +29 -0
  108. diskstack/formats.py +381 -0
  109. diskstack/parallel.py +73 -0
  110. diskstack/report.py +353 -0
  111. diskstack/stack.py +317 -0
  112. diskstack-1.0.0.dist-info/METADATA +329 -0
  113. diskstack-1.0.0.dist-info/RECORD +119 -0
  114. diskstack-1.0.0.dist-info/WHEEL +5 -0
  115. diskstack-1.0.0.dist-info/entry_points.txt +2 -0
  116. diskstack-1.0.0.dist-info/licenses/LICENSE +21 -0
  117. diskstack-1.0.0.dist-info/licenses/NOTICE +55 -0
  118. diskstack-1.0.0.dist-info/licenses/diskstack/_vendor/greaseweazle/COPYING +24 -0
  119. diskstack-1.0.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,238 @@
1
+ # greaseweazle/codec/macintosh/mac_gcr.py
2
+ #
3
+ # The layout generated by master_track() matches as closely as possible a
4
+ # raw dump I received of the first disk of the Italian release of stylewriter.
5
+ # It differs slightly from the layout documented by MAME and generated by
6
+ # FluxEngine, though I am sure in no important respects.
7
+ #
8
+ # Written & released by Keir Fraser <keir.xen@gmail.com>
9
+ #
10
+ # This is free and unencumbered software released into the public domain.
11
+ # See the file COPYING for more details, or visit <http://unlicense.org>.
12
+
13
+ from typing import List, Optional, Tuple
14
+
15
+ import struct
16
+ import itertools as it
17
+ from bitarray import bitarray
18
+
19
+ from diskstack._vendor.greaseweazle import error
20
+ from diskstack._vendor.greaseweazle import optimised
21
+ from diskstack._vendor.greaseweazle.codec import codec
22
+ from diskstack._vendor.greaseweazle.codec.ibm import ibm
23
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
24
+ from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
25
+
26
+ default_revs = 1.3
27
+
28
+ self_sync_bytes = b'\xff\x3f\xcf\xf3\xfc\xff'
29
+ sector_sync_bytes = b'\xd5\xaa\x96' # 1101 0101 1010 1010 1001 0110
30
+ data_sync_bytes = b'\xd5\xaa\xad' # 1101 0101 1010 1010 1010 1101
31
+
32
+ sector_sync = bitarray(endian='big')
33
+ sector_sync.frombytes(sector_sync_bytes)
34
+
35
+ data_sync = bitarray(endian='big')
36
+ data_sync.frombytes(data_sync_bytes)
37
+
38
+ seclen = 524
39
+ enc_seclen = 703
40
+
41
+ bad_sector = b'-=[BAD SECTOR]=-' * 32
42
+
43
+ class MacGCR(codec.Codec):
44
+
45
+ time_per_rev = 0.2
46
+
47
+ verify_revs = default_revs
48
+
49
+ def __init__(self, cyl: int, head: int, config):
50
+ self.cyl, self.head = cyl, head
51
+ self.config = config
52
+ self.clock = config.clock
53
+ self.sector: List[Optional[bytes]]
54
+ self.sector = [None] * self.nsec
55
+ sec_map, pos = [-1] * self.nsec, 0
56
+ for i in range(self.nsec):
57
+ while sec_map[pos] != -1:
58
+ pos = (pos + 1) % self.nsec
59
+ sec_map[pos] = i
60
+ pos = (pos + config.interleave) % self.nsec
61
+ self.sec_map = sec_map
62
+ error.check(optimised.enabled,
63
+ 'Macintosh GCR requires optimised C extension')
64
+
65
+ @property
66
+ def nsec(self) -> int:
67
+ return self.config.secs
68
+
69
+ def summary_string(self) -> str:
70
+ nsec, nbad = self.nsec, self.nr_missing()
71
+ s = "Macintosh GCR (%d/%d sectors)" % (nsec - nbad, nsec)
72
+ return s
73
+
74
+ # private
75
+ def add(self, sec_id, data) -> None:
76
+ assert not self.has_sec(sec_id)
77
+ self.sector[sec_id] = data
78
+
79
+ def has_sec(self, sec_id: int) -> bool:
80
+ return self.sector[sec_id] is not None
81
+
82
+ def nr_missing(self) -> int:
83
+ return len([sec for sec in self.sector if sec is None])
84
+
85
+ def get_img_track(self) -> bytearray:
86
+ tdat = bytearray()
87
+ for sec in self.sector:
88
+ tdat += sec[12:] if sec is not None else bad_sector
89
+ return tdat
90
+
91
+ def set_img_track(self, tdat: bytes) -> int:
92
+ totsize = self.nsec * 512
93
+ if len(tdat) < totsize:
94
+ tdat += bytes(totsize - len(tdat))
95
+ for sec in range(self.nsec):
96
+ self.sector[sec] = bytes(12) + tdat[sec*512:(sec+1)*512]
97
+ return totsize
98
+
99
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
100
+ raw = PLLTrack(time_per_rev = self.time_per_rev,
101
+ clock = self.clock, data = track, pll = pll)
102
+ bits, _ = raw.get_all_data()
103
+
104
+ for offs in bits.search(sector_sync):
105
+
106
+ if self.nr_missing() == 0:
107
+ break
108
+
109
+ # Decode header
110
+ offs += 3*8
111
+ sec = bits[offs:offs+5*8].tobytes()
112
+ if len(sec) != 5:
113
+ continue
114
+ hdr = optimised.decode_mac_gcr(sec)
115
+ sum = 0
116
+ for x in hdr:
117
+ sum ^= x
118
+ if sum != 0:
119
+ continue
120
+ cyl, sec_id, side, fmt = tuple(hdr[:4])
121
+ cyl |= (side & 1) << 6
122
+ side >>= 5
123
+ if (cyl != self.cyl or side != self.head or sec_id >= self.nsec):
124
+ print('T%d.%d: Ignoring unexpected sector '
125
+ 'C:%d H:%d R:%d F:0x%x'
126
+ % (self.cyl, self.head, cyl, side, sec_id, fmt))
127
+ continue
128
+ if self.has_sec(sec_id):
129
+ continue
130
+
131
+ # Find data
132
+ offs += 5*8
133
+ dat_offs = list(bits[offs:offs+100*8].search(data_sync))
134
+ if len(dat_offs) != 1:
135
+ continue
136
+ offs += dat_offs[0]
137
+
138
+ # Decode data
139
+ offs += 4*8
140
+ sec = bits[offs:offs+703*8].tobytes()
141
+ if len(sec) != 703:
142
+ continue
143
+ sec = optimised.decode_mac_gcr(sec)
144
+ sec, csum = optimised.decode_mac_sector(sec)
145
+ if csum != 0:
146
+ continue
147
+
148
+ self.add(sec_id, sec)
149
+
150
+
151
+ def master_track(self) -> MasterTrack:
152
+
153
+ # Post-index track gap.
154
+ t = bytes([0x96] * 64) + self_sync_bytes * 20
155
+
156
+ for nr, sec_id in enumerate(self.sec_map):
157
+ sector = self.sector[sec_id]
158
+ data = (bytes(12) + bad_sector) if sector is None else sector
159
+ cyl, side = self.cyl, self.head
160
+ side = (side << 5) | (cyl >> 6)
161
+ cyl &= 0x3f
162
+ hdr = bytes([cyl, sec_id, side, self.config.format])
163
+ sum = 0
164
+ for x in hdr:
165
+ sum ^= x
166
+ t += self_sync_bytes * 6
167
+ t += sector_sync_bytes
168
+ t += optimised.encode_mac_gcr(hdr + bytes([sum]))
169
+ t += b'\xde\xaa\xff\xff'
170
+ t += self_sync_bytes
171
+ t += data_sync_bytes
172
+ t += optimised.encode_mac_gcr(bytes([sec_id]))
173
+ t += optimised.encode_mac_gcr(optimised.encode_mac_sector(data))
174
+ t += b'\xde\xaa\xff'
175
+
176
+ # Add the pre-index gap.
177
+ t += b'\xff' * 4
178
+ tlen = int((self.time_per_rev / self.clock)) & ~31
179
+ t += bytes([0x96] * (tlen//8-len(t)))
180
+
181
+ track = MasterTrack(bits = t, time_per_rev = self.time_per_rev)
182
+ track.verify = self
183
+ return track
184
+
185
+
186
+ def verify_track(self, flux):
187
+ readback_track = self.__class__(self.cyl, self.head, self.config)
188
+ readback_track.decode_flux(flux)
189
+ return (readback_track.nr_missing() == 0
190
+ and self.sector == readback_track.sector)
191
+
192
+
193
+ class MacGCRDef(codec.TrackDef):
194
+
195
+ default_revs = default_revs
196
+
197
+ def __init__(self, format_name: str):
198
+ self.secs: Optional[int] = None
199
+ self.clock: Optional[float] = None
200
+ self.format: Optional[int] = None
201
+ self.interleave = 1
202
+ self.finalised = False
203
+
204
+ def add_param(self, key: str, val) -> None:
205
+ if key == 'secs':
206
+ val = int(val)
207
+ self.secs = val
208
+ elif key == 'clock':
209
+ val = float(val)
210
+ self.clock = val * 1e-6
211
+ elif key == 'format':
212
+ val = int(val, base=0)
213
+ error.check(0 <= val <= 255, '%s out of range' % key)
214
+ self.format = val
215
+ elif key == 'interleave':
216
+ val = int(val)
217
+ self.interleave = val
218
+ else:
219
+ raise error.Fatal('unrecognised track option %s' % key)
220
+
221
+ def finalise(self) -> None:
222
+ if self.finalised:
223
+ return
224
+ error.check(self.secs is not None,
225
+ 'number of sectors not specified')
226
+ error.check(self.clock is not None,
227
+ 'clock period not specified')
228
+ error.check(self.format is not None,
229
+ 'format byte not specified')
230
+ self.finalised = True
231
+
232
+ def mk_track(self, cyl: int, head: int) -> MacGCR:
233
+ return MacGCR(cyl, head, self)
234
+
235
+
236
+ # Local variables:
237
+ # python-indent: 4
238
+ # End:
@@ -0,0 +1,233 @@
1
+ # greaseweazle/codec/hp/micropolis.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
+
7
+ from typing import List, Optional
8
+
9
+ import struct
10
+ from bitarray import bitarray
11
+ import crcmod.predefined
12
+ import itertools as it
13
+ from enum import Enum
14
+
15
+ from diskstack._vendor.greaseweazle import error
16
+ from diskstack._vendor.greaseweazle.codec import codec
17
+ from diskstack._vendor.greaseweazle.codec.ibm.ibm import decode, encode, fm_encode, mfm_encode
18
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
19
+ from diskstack._vendor.greaseweazle.flux import HasFlux
20
+
21
+ default_revs = 1
22
+
23
+ bad_sector = b'-=[BAD SECTOR]=-'
24
+
25
+ mfm_sync = bitarray(endian='big')
26
+ mfm_sync.frombytes(mfm_encode(encode(b'\x00\x00\x00\xff')))
27
+
28
+ # Sector format is:
29
+ # 40: 00 preamble
30
+ # 1: FF sync
31
+ # 2: track, sector
32
+ # 10: padding/vendor
33
+ # 256: payload
34
+ # 1: checksum
35
+ # 5: ecc [optional]
36
+ # NN: 00 postamble
37
+
38
+ def micropolis_csum(dat):
39
+ y = 0
40
+ for x in dat:
41
+ if y > 255:
42
+ y -= 255
43
+ y += x
44
+ # Ignore carry on last addition
45
+ return y & 255
46
+
47
+ class Micropolis(codec.Codec):
48
+
49
+ time_per_rev = 0.2
50
+
51
+ verify_revs: float = default_revs
52
+
53
+ def __init__(self, cyl: int, head: int, config):
54
+ self.clock = 2e-6
55
+ self.presync_bytes = 40
56
+ self.cyl, self.head = cyl, head
57
+ self.config = config
58
+ self.sector: List[Optional[bytes]]
59
+ self.sector = [None] * self.nsec
60
+
61
+ @property
62
+ def nsec(self) -> int:
63
+ return self.config.secs
64
+
65
+ @property
66
+ def img_bps(self) -> int:
67
+ return self.config.img_bps
68
+
69
+ def summary_string(self) -> str:
70
+ nsec, nbad = self.nsec, self.nr_missing()
71
+ s = "Micropolis (%d/%d sectors)" % (nsec - nbad, nsec)
72
+ return s
73
+
74
+ def bad_sector(self) -> bytes:
75
+ if self.img_bps == 256:
76
+ return bad_sector * 16
77
+ return b'\xff' + bytes(12) + bad_sector*16 + bytes(6)
78
+
79
+ # private
80
+ def add(self, sec_id, data) -> None:
81
+ assert not self.has_sec(sec_id)
82
+ self.sector[sec_id] = data
83
+
84
+ def has_sec(self, sec_id: int) -> bool:
85
+ return self.sector[sec_id] is not None
86
+
87
+ def nr_missing(self) -> int:
88
+ return len([sec for sec in self.sector if sec is None])
89
+
90
+ def get_img_track(self) -> bytearray:
91
+ tdat = bytearray()
92
+ for sec in self.sector:
93
+ tdat += sec if sec is not None else self.bad_sector()
94
+ return tdat
95
+
96
+ def set_img_track(self, tdat: bytes) -> int:
97
+ totsize = self.nsec * self.img_bps
98
+ if len(tdat) < totsize:
99
+ tdat += bytes(totsize - len(tdat))
100
+ for sec in range(self.nsec):
101
+ self.sector[sec] = tdat[sec*self.img_bps:(sec+1)*self.img_bps]
102
+ return totsize
103
+
104
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
105
+ flux = track.flux()
106
+ if flux.time_per_rev < self.time_per_rev / 2:
107
+ flux.identify_hard_sectors()
108
+ flux.cue_at_index()
109
+ raw = PLLTrack(time_per_rev = self.time_per_rev,
110
+ clock = self.clock, data = flux, pll = pll)
111
+
112
+ for rev in range(len(raw.revolutions)):
113
+
114
+ if self.nr_missing() == 0:
115
+ break
116
+
117
+ bits, _ = raw.get_revolution(rev)
118
+
119
+ hardsector_bits = raw.revolutions[rev].hardsector_bits
120
+ if hardsector_bits is not None:
121
+ hardsector_bits = list(it.accumulate(hardsector_bits))
122
+ else:
123
+ hardsector_bits = [len(bits)*(i+1)//self.nsec
124
+ for i in range(self.nsec)]
125
+ error.check(len(hardsector_bits) == self.nsec,
126
+ f'North Star: Unexpected number of sectors: '
127
+ f'{len(hardsector_bits)}')
128
+ hardsector_bits = [0] + hardsector_bits
129
+
130
+ for hsec_id in range(self.nsec):
131
+
132
+ s, e = hardsector_bits[hsec_id], hardsector_bits[hsec_id+1]
133
+ # Skip first 50 cells (~100us) to avoid false sync
134
+ # Reference: Vector Micropolis Disk Controller Board Technical
135
+ # Information Manual, pp. 1-16.
136
+ s += 50
137
+ offs = bits[s:e].search(mfm_sync)
138
+ for off in offs:
139
+ off += 3*16
140
+ dat = decode(bits[s+off:s+off+275*16].tobytes())
141
+ if len(dat) != 275:
142
+ continue
143
+ cyl, sec_id = dat[1], dat[2]
144
+ if cyl != self.cyl or sec_id > self.nsec:
145
+ print('T%d.%d: Ignoring unexpected sector C:%d R:%d'
146
+ % (self.cyl, self.head, cyl, sec_id))
147
+ continue
148
+ if self.has_sec(sec_id):
149
+ continue
150
+ if micropolis_csum(dat[1:-6]) == dat[-6]:
151
+ if hsec_id != sec_id:
152
+ print(f'T{self.cyl, self.head}: Weird skew: '
153
+ f'hsec {hsec_id} contains sector {sec_id}')
154
+ if self.img_bps == 256:
155
+ self.add(sec_id, dat[13:13+256])
156
+ else:
157
+ self.add(sec_id, dat)
158
+ break # We are done on this hard sector
159
+
160
+
161
+ def master_track(self) -> MasterTrack:
162
+
163
+ t = bytes()
164
+ slen = int((self.time_per_rev / self.clock / self.nsec / 16))
165
+
166
+ for sec_id in range(self.nsec):
167
+ s = encode(bytes(self.presync_bytes))
168
+ sector = self.sector[sec_id]
169
+ sector = self.bad_sector() if sector is None else sector
170
+ if len(sector) == 256:
171
+ dat = b'\xff' + bytes([self.cyl, sec_id]) + bytes(10)
172
+ dat += sector
173
+ dat += bytes([micropolis_csum(dat[1:])])
174
+ else:
175
+ if sector[0] != 0xff:
176
+ print("T%u.%u: 275-byte sector doesn't start with FF sync"
177
+ % (self.cyl, self.head))
178
+ dat = sector
179
+ s += encode(dat)
180
+ s += encode(bytes(slen - len(s)//2))
181
+ t += s
182
+
183
+ t = mfm_encode(t)
184
+
185
+ track = MasterTrack(bits = t, time_per_rev = self.time_per_rev,
186
+ hardsector_bits = [slen*16] * self.nsec)
187
+ track.verify = self
188
+ return track
189
+
190
+
191
+ def verify_track(self, flux):
192
+ readback_track = self.__class__(self.cyl, self.head, self.config)
193
+ readback_track.decode_flux(flux)
194
+ return (readback_track.nr_missing() == 0
195
+ and self.sector == readback_track.sector)
196
+
197
+
198
+ class MicropolisDef(codec.TrackDef):
199
+
200
+ default_revs = default_revs
201
+
202
+ def __init__(self, format_name: str):
203
+ self.secs: Optional[int] = None
204
+ self.img_bps: Optional[int] = None
205
+ self.finalised = False
206
+
207
+ def add_param(self, key: str, val) -> None:
208
+ if key == 'secs':
209
+ val = int(val)
210
+ self.secs = val
211
+ elif key == 'img_bps':
212
+ val = int(val)
213
+ self.img_bps = val
214
+ error.check(val in [256, 275], f'bad img_bps {val}')
215
+ else:
216
+ raise error.Fatal('unrecognised track option %s' % key)
217
+
218
+ def finalise(self) -> None:
219
+ if self.finalised:
220
+ return
221
+ error.check(self.secs is not None,
222
+ 'number of sectors not specified')
223
+ error.check(self.img_bps is not None,
224
+ 'img_bps not specified')
225
+ self.finalised = True
226
+
227
+ def mk_track(self, cyl: int, head: int) -> Micropolis:
228
+ return Micropolis(cyl, head, self)
229
+
230
+
231
+ # Local variables:
232
+ # python-indent: 4
233
+ # End:
@@ -0,0 +1,210 @@
1
+ # greaseweazle/codec/hp/northstar.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
+
7
+ from typing import List, Optional
8
+
9
+ import struct
10
+ from bitarray import bitarray
11
+ import crcmod.predefined
12
+ import itertools as it
13
+ from enum import Enum
14
+
15
+ from diskstack._vendor.greaseweazle import error
16
+ from diskstack._vendor.greaseweazle.codec import codec
17
+ from diskstack._vendor.greaseweazle.codec.ibm.ibm import decode, encode, fm_encode, mfm_encode
18
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
19
+ from diskstack._vendor.greaseweazle.flux import HasFlux
20
+
21
+ default_revs = 1
22
+
23
+ bad_sector = b'-=[BAD SECTOR]=-'
24
+
25
+ mfm_sync = bitarray(endian='big')
26
+ mfm_sync.frombytes(mfm_encode(encode(b'\x00\xfb')))
27
+
28
+ fm_sync = bitarray(endian='big')
29
+ fm_sync.frombytes(fm_encode(encode(b'\x00\xfb')))
30
+
31
+ def csum(dat):
32
+ y = 0
33
+ for x in dat:
34
+ y ^= x
35
+ y = ((y << 1) | (y >> 7)) & 255
36
+ return y
37
+
38
+ class Mode(Enum):
39
+ FM, MFM = range(2)
40
+ def __str__(self):
41
+ NAMES = [ 'North Star FM', 'North Star MFM' ]
42
+ return f'{NAMES[self.value]}'
43
+
44
+ class NorthStar(codec.Codec):
45
+
46
+ time_per_rev = 0.2
47
+
48
+ verify_revs: float = default_revs
49
+
50
+ def __init__(self, cyl: int, head: int, config):
51
+ if config.mode is Mode.FM:
52
+ self.clock = 4e-6
53
+ self.bps = 256
54
+ self.sync = fm_sync
55
+ self.presync_bytes = 17
56
+ self.sync_bytes = 1
57
+ else:
58
+ self.clock = 2e-6
59
+ self.bps = 512
60
+ self.sync = mfm_sync
61
+ self.presync_bytes = 34
62
+ self.sync_bytes = 2
63
+ self.cyl, self.head = cyl, head
64
+ self.config = config
65
+ self.sector: List[Optional[bytes]]
66
+ self.sector = [None] * self.nsec
67
+
68
+ @property
69
+ def nsec(self) -> int:
70
+ return self.config.secs
71
+
72
+ def summary_string(self) -> str:
73
+ nsec, nbad = self.nsec, self.nr_missing()
74
+ s = "%s (%d/%d sectors)" % (self.config.mode, nsec - nbad, nsec)
75
+ return s
76
+
77
+ # private
78
+ def add(self, sec_id, data) -> None:
79
+ assert not self.has_sec(sec_id)
80
+ self.sector[sec_id] = data
81
+
82
+ def has_sec(self, sec_id: int) -> bool:
83
+ return self.sector[sec_id] is not None
84
+
85
+ def nr_missing(self) -> int:
86
+ return len([sec for sec in self.sector if sec is None])
87
+
88
+ def get_img_track(self) -> bytearray:
89
+ tdat = bytearray()
90
+ for sec in self.sector:
91
+ tdat += sec if sec is not None else bad_sector * (self.bps//16)
92
+ return tdat
93
+
94
+ def set_img_track(self, tdat: bytes) -> int:
95
+ totsize = self.nsec * self.bps
96
+ if len(tdat) < totsize:
97
+ tdat += bytes(totsize - len(tdat))
98
+ for sec in range(self.nsec):
99
+ self.sector[sec] = tdat[sec*self.bps:(sec+1)*self.bps]
100
+ return totsize
101
+
102
+ def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
103
+ flux = track.flux()
104
+ if flux.time_per_rev < self.time_per_rev / 2:
105
+ flux.identify_hard_sectors()
106
+ flux.cue_at_index()
107
+ raw = PLLTrack(time_per_rev = self.time_per_rev,
108
+ clock = self.clock, data = flux, pll = pll)
109
+
110
+ for rev in range(len(raw.revolutions)):
111
+
112
+ if self.nr_missing() == 0:
113
+ break
114
+
115
+ bits, _ = raw.get_revolution(rev)
116
+
117
+ hardsector_bits = raw.revolutions[rev].hardsector_bits
118
+ if hardsector_bits is not None:
119
+ hardsector_bits = list(it.accumulate(hardsector_bits))
120
+ else:
121
+ hardsector_bits = [len(bits)*(i+1)//self.nsec
122
+ for i in range(self.nsec)]
123
+ error.check(len(hardsector_bits) == self.nsec,
124
+ f'North Star: Unexpected number of sectors: '
125
+ f'{len(hardsector_bits)}')
126
+ hardsector_bits = [0] + hardsector_bits
127
+
128
+ for sec_id in range(self.nsec):
129
+
130
+ if self.has_sec(sec_id):
131
+ continue
132
+
133
+ s, e = hardsector_bits[sec_id], hardsector_bits[sec_id+1]
134
+ offs = bits[s:e].search(self.sync)
135
+ if (off := next(offs, None)) is None:
136
+ continue
137
+ off += (1 + self.sync_bytes) * 16
138
+ data = decode(bits[s+off:s+off+(self.bps+1)*16].tobytes())
139
+ if csum(data[:-1]) == data[-1]:
140
+ self.add(sec_id, data[:-1])
141
+
142
+
143
+ def master_track(self) -> MasterTrack:
144
+
145
+ t = bytes()
146
+ slen = int((self.time_per_rev / self.clock / self.nsec / 16))
147
+
148
+ for sec_id in range(self.nsec):
149
+ s = encode(bytes(self.presync_bytes))
150
+ s += encode(b'\xfb' * self.sync_bytes)
151
+ sector = self.sector[sec_id]
152
+ data = bad_sector*(self.bps//16) if sector is None else sector
153
+ s += encode(data + bytes([csum(data)]))
154
+ s += encode(bytes(slen - len(s)//2))
155
+ t += s
156
+
157
+ t = mfm_encode(t) if self.config.mode is Mode.MFM else fm_encode(t)
158
+
159
+ track = MasterTrack(bits = t, time_per_rev = self.time_per_rev,
160
+ hardsector_bits = [slen*16] * self.nsec)
161
+ track.verify = self
162
+ return track
163
+
164
+
165
+ def verify_track(self, flux):
166
+ readback_track = self.__class__(self.cyl, self.head, self.config)
167
+ readback_track.decode_flux(flux)
168
+ return (readback_track.nr_missing() == 0
169
+ and self.sector == readback_track.sector)
170
+
171
+
172
+ class NorthStarDef(codec.TrackDef):
173
+
174
+ default_revs = default_revs
175
+
176
+ def __init__(self, format_name: str):
177
+ self.mode: Optional[Mode] = None
178
+ self.secs: Optional[int] = None
179
+ self.finalised = False
180
+
181
+ def add_param(self, key: str, val) -> None:
182
+ if key == 'secs':
183
+ val = int(val)
184
+ self.secs = val
185
+ elif key == 'mode':
186
+ if val == 'fm':
187
+ self.mode = Mode.FM
188
+ elif val == 'mfm':
189
+ self.mode = Mode.MFM
190
+ else:
191
+ raise error.Fatal('unrecognised mode %s' % val)
192
+ else:
193
+ raise error.Fatal('unrecognised track option %s' % key)
194
+
195
+ def finalise(self) -> None:
196
+ if self.finalised:
197
+ return
198
+ error.check(self.secs is not None,
199
+ 'number of sectors not specified')
200
+ error.check(self.mode is not None,
201
+ 'mode not specified')
202
+ self.finalised = True
203
+
204
+ def mk_track(self, cyl: int, head: int) -> NorthStar:
205
+ return NorthStar(cyl, head, self)
206
+
207
+
208
+ # Local variables:
209
+ # python-indent: 4
210
+ # End:
File without changes