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,304 @@
1
+ # greaseweazle/image/kryoflux.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
+
8
+ from typing import Optional
9
+
10
+ import struct, re, math, os, datetime
11
+ import itertools as it
12
+
13
+ from diskstack._vendor.greaseweazle import __version__
14
+ from diskstack._vendor.greaseweazle import error
15
+ from diskstack._vendor.greaseweazle.flux import Flux
16
+ from .image import Image, ImageOpts, OptDict
17
+
18
+ def_mck = 18432000 * 73 / 14 / 2
19
+ def_sck = def_mck / 2
20
+
21
+ class Op:
22
+ Nop1 = 8
23
+ Nop2 = 9
24
+ Nop3 = 10
25
+ Ovl16 = 11
26
+ Flux3 = 12
27
+ OOB = 13
28
+
29
+ class OOB:
30
+ StreamInfo = 1
31
+ Index = 2
32
+ StreamEnd = 3
33
+ KFInfo = 4
34
+ EOF = 13
35
+
36
+ class KFOpts(ImageOpts):
37
+ """sck: Sample clock to use for flux timings.
38
+ Suffix 'm' for MHz. For example: sck=72m
39
+ revs: Number of revolutions to output per track.
40
+ """
41
+
42
+ w_settings = [ 'sck', 'revs' ]
43
+
44
+ def __init__(self) -> None:
45
+ self._sck: float = def_sck
46
+ self._revs: Optional[int] = None
47
+
48
+ @property
49
+ def sck(self) -> float:
50
+ return self._sck
51
+ @sck.setter
52
+ def sck(self, sck: str):
53
+ factor = 1e0
54
+ if sck.lower().endswith('m'):
55
+ sck = sck[:-1]
56
+ factor = 1e6
57
+ try:
58
+ self._sck = float(sck) * factor
59
+ except ValueError:
60
+ raise error.Fatal("Kryoflux: Bad sck value: '%s'\n" % sck)
61
+
62
+ @property
63
+ def revs(self) -> Optional[int]:
64
+ return self._revs
65
+ @revs.setter
66
+ def revs(self, revs: str) -> None:
67
+ try:
68
+ self._revs = int(revs)
69
+ if self._revs < 1:
70
+ raise ValueError
71
+ except ValueError:
72
+ raise error.Fatal("Kryoflux: Invalid revs: '%s'" % revs)
73
+
74
+
75
+ class KryoFlux(Image):
76
+
77
+ def __init__(self, name: str, _fmt) -> None:
78
+ m = re.search(r'\d{2}\.[01]\.raw$', name, flags=re.IGNORECASE)
79
+ error.check(
80
+ m is not None,
81
+ '''\
82
+ Bad Kryoflux image name pattern '%s'
83
+ Name pattern must be path/to/nameNN.N.raw (N is a digit)'''
84
+ % name)
85
+ assert m is not None # mypy
86
+ self.basename = name[:m.start()]
87
+ self.filename = name
88
+ self.opts = KFOpts()
89
+
90
+
91
+ @classmethod
92
+ def from_file(cls, name: str, _fmt, opts: OptDict) -> Image:
93
+ # Check that the specified raw file actually exists.
94
+ with open(name, 'rb') as _:
95
+ pass
96
+ obj = cls(name, _fmt)
97
+ obj.apply_r_opts(opts)
98
+ return obj
99
+
100
+
101
+ def get_track(self, cyl, side):
102
+
103
+ name = self.basename + '%02d.%d.raw' % (cyl, side)
104
+ try:
105
+ with open(name, 'rb') as f:
106
+ dat = f.read()
107
+ except FileNotFoundError:
108
+ return None
109
+
110
+ # Parse the index-pulse stream positions and KFInfo blocks.
111
+ index, idx = [], 0
112
+ sck, ick = self.opts.sck, self.opts.sck/8
113
+ while idx < len(dat):
114
+ op = dat[idx]
115
+ if op == Op.OOB:
116
+ oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
117
+ idx += 4
118
+ if oob_op == OOB.Index:
119
+ pos, = struct.unpack('<I', dat[idx:idx+4])
120
+ index.append(pos)
121
+ elif oob_op == OOB.EOF:
122
+ break
123
+ elif oob_op == OOB.KFInfo:
124
+ info = dat[idx:idx+oob_sz-1].decode('utf-8', 'ignore')
125
+ m = re.search(r'sck=([^,]+)', info)
126
+ if m is not None:
127
+ sck = float(m.group(1))
128
+ m = re.search(r'ick=([^,]+)', info)
129
+ if m is not None:
130
+ ick = float(m.group(1))
131
+ idx += oob_sz
132
+ elif op == Op.Nop3 or op == Op.Flux3:
133
+ idx += 3
134
+ elif op <= 7 or op == Op.Nop2:
135
+ idx += 2
136
+ else:
137
+ idx += 1
138
+
139
+ # Build the flux and index lists for the Flux object.
140
+ flux, flux_list, index_list = [], [], []
141
+ val, index_idx, stream_idx, idx = 0, 0, 0, 0
142
+ while idx < len(dat):
143
+ if index_idx < len(index) and stream_idx >= index[index_idx]:
144
+ # We've passed an index marker.
145
+ index_list.append(sum(flux))
146
+ flux_list += flux
147
+ flux = []
148
+ index_idx += 1
149
+ op = dat[idx]
150
+ if op <= 7:
151
+ # Flux2
152
+ val += (op << 8) + dat[idx+1]
153
+ flux.append(val)
154
+ val = 0
155
+ stream_idx += 2
156
+ idx += 2
157
+ elif op <= 10:
158
+ # Nop1, Nop2, Nop3
159
+ nr = op-7
160
+ stream_idx += nr
161
+ idx += nr
162
+ elif op == Op.Ovl16:
163
+ # Ovl16
164
+ val += 0x10000
165
+ stream_idx += 1
166
+ idx += 1
167
+ elif op == Op.Flux3:
168
+ # Flux3
169
+ val += (dat[idx+1] << 8) + dat[idx+2]
170
+ flux.append(val)
171
+ val = 0
172
+ stream_idx += 3
173
+ idx += 3
174
+ elif op == Op.OOB:
175
+ # OOB
176
+ oob_op, oob_sz = struct.unpack('<BH', dat[idx+1:idx+4])
177
+ idx += 4
178
+ if oob_op == OOB.StreamInfo or oob_op == OOB.StreamEnd:
179
+ pos, = struct.unpack('<I', dat[idx:idx+4])
180
+ error.check(pos == stream_idx,
181
+ "Out-of-sync during KryoFlux stream read")
182
+ elif oob_op == OOB.EOF:
183
+ break
184
+ idx += oob_sz
185
+ else:
186
+ # Flux1
187
+ val += op
188
+ flux.append(val)
189
+ val = 0
190
+ stream_idx += 1
191
+ idx += 1
192
+
193
+ flux_list += flux
194
+
195
+ # Crop partial first revolution.
196
+ if len(index_list) > 1:
197
+ short_index, index_list = index_list[0], index_list[1:]
198
+ flux = 0
199
+ for i in range(len(flux_list)):
200
+ if flux >= short_index:
201
+ break
202
+ flux += flux_list[i]
203
+ flux_list = flux_list[i:]
204
+
205
+ return Flux(index_list, flux_list, sck)
206
+
207
+
208
+ def emit_track(self, cyl, side, track):
209
+ """Converts @track into a KryoFlux stream file."""
210
+
211
+ # Check if we should insert an OOB record for the next index mark.
212
+ def check_index(prev_flux):
213
+ nonlocal index_idx, dat
214
+ if index_idx < len(index) and total >= index[index_idx]:
215
+ dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12,
216
+ stream_idx,
217
+ round(index[index_idx] - total + prev_flux),
218
+ round(index[index_idx]/8))
219
+ index_idx += 1
220
+
221
+ # Emit a resampled flux value to the KryoFlux data stream.
222
+ def emit(f):
223
+ nonlocal stream_idx, dat, total
224
+ while f >= 0x10000:
225
+ stream_idx += 1
226
+ dat.append(Op.Ovl16)
227
+ f -= 0x10000
228
+ total += 0x10000
229
+ check_index(0x10000)
230
+ if f >= 0x800:
231
+ stream_idx += 3
232
+ dat += struct.pack('>BH', Op.Flux3, f)
233
+ elif Op.OOB < f < 0x100:
234
+ stream_idx += 1
235
+ dat.append(f)
236
+ else:
237
+ stream_idx += 2
238
+ dat += struct.pack('>H', f)
239
+ total += f
240
+ check_index(f)
241
+
242
+ flux = track.flux()
243
+ sck = self.opts.sck
244
+
245
+ # HxC crashes or fails to load non-index-cued stream files.
246
+ # So let's give it what it wants.
247
+ flux.cue_at_index()
248
+
249
+ if self.opts.revs is not None:
250
+ flux.set_nr_revs(self.opts.revs)
251
+
252
+ factor = sck / flux.sample_freq
253
+ dat = bytearray()
254
+
255
+ now = datetime.datetime.now()
256
+ host_date = now.strftime('%Y.%m.%d')
257
+ host_time = now.strftime('%H:%M:%S')
258
+ info = (f'name=Greaseweazle, version={__version__}, '
259
+ f'host_date={host_date}, host_time={host_time}, '
260
+ f'sck={sck:.7f}, ick={sck/8:.7f}')
261
+ dat += struct.pack('<2BH', Op.OOB, OOB.KFInfo, len(info)+1)
262
+ dat += info.encode('utf-8') + bytes(1)
263
+
264
+ # Start the data stream with a dummy index if our Flux is index cued.
265
+ if flux.index_cued:
266
+ dat += struct.pack('<2BH3I', Op.OOB, OOB.Index, 12, 0, 0, 0)
267
+
268
+ # Prefix-sum list of resampled index timings.
269
+ index = list(it.accumulate(map(lambda x: x*factor, flux.index_list)))
270
+ index_idx = 0
271
+
272
+ stream_idx, total, rem = 0, 0, 0.0
273
+ for x in flux.list:
274
+ y = x * factor + rem
275
+ f = round(y)
276
+ rem = y - f
277
+ emit(f)
278
+
279
+ # We may not have enough flux to get to the final index value.
280
+ # Generate a dummy flux just enough to get us there.
281
+ if index_idx < len(index):
282
+ emit(math.ceil(index[index_idx] - total) + 1)
283
+ # A dummy cell so that we definitely have *something* after the
284
+ # final OOB.Index, so that all parsers should register the Index.
285
+ emit(round(sck*12e-6)) # 12us
286
+
287
+ # Emit StreamEnd and EOF blocks to terminate the stream.
288
+ dat += struct.pack('<2BH2I', Op.OOB, OOB.StreamEnd, 8, stream_idx, 0)
289
+ dat += struct.pack('<2BH', Op.OOB, OOB.EOF, 0x0d0d)
290
+
291
+ name = self.basename + '%02d.%d.raw' % (cyl, side)
292
+ with open(name, ('wb','xb')[self.noclobber]) as f:
293
+ f.write(dat)
294
+
295
+
296
+ def __enter__(self):
297
+ return self
298
+ def __exit__(self, type, value, tb):
299
+ pass
300
+
301
+
302
+ # Local variables:
303
+ # python-indent: 4
304
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/mgt.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
+
8
+ from diskstack._vendor.greaseweazle.image.img import IMG
9
+
10
+ class MGT(IMG):
11
+ default_format = 'ibm.800'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End:
@@ -0,0 +1,153 @@
1
+ # greaseweazle/image/msa.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
+
8
+ from typing import Dict, Tuple, Optional
9
+
10
+ import struct
11
+
12
+ from diskstack._vendor.greaseweazle import error
13
+ from diskstack._vendor.greaseweazle.codec.ibm import ibm
14
+ from .image import Image
15
+
16
+ class MSA(Image):
17
+
18
+ def __init__(self, name: str, _fmt):
19
+ self.to_track: Dict[Tuple[int,int],ibm.IBMTrack_Fixed] = dict()
20
+ self.filename = name
21
+
22
+
23
+ def from_bytes(self, dat: bytes) -> None:
24
+
25
+ id, spt, nsides, st, et = struct.unpack('>2s4H', dat[0:10])
26
+
27
+ error.check(id == b'\x0e\x0f', 'MSA: Unrecognised signature')
28
+
29
+ nsides += 1
30
+ error.check(1 <= nsides <= 2, f'MSA: Bad number of sides: {nsides}')
31
+
32
+ idx = 10
33
+ for cyl in range(st, et+1):
34
+ for head in range(nsides):
35
+ nbytes, = struct.unpack('>H', dat[idx:idx+2])
36
+ error.check(nbytes <= spt*512, 'MSA: Track data too long')
37
+ idx += 2
38
+ td = dat[idx:idx+nbytes]
39
+ idx += nbytes
40
+
41
+ if nbytes == spt*512:
42
+ tdat = td
43
+ else:
44
+ tdat = bytearray()
45
+ tidx = 0
46
+ while tidx < len(td):
47
+ b, tidx = td[tidx], tidx+1
48
+ if b == 0xe5:
49
+ b, runlen = struct.unpack('>BH', td[tidx:tidx+3])
50
+ tidx += 3
51
+ tdat += bytes([b]) * runlen
52
+ else:
53
+ tdat.append(b)
54
+ error.check(len(tdat) == spt*512,
55
+ 'MSA: Bad track compressed data')
56
+
57
+ track = ibm.IBMTrack_FixedDef('ibm.mfm')
58
+ track.iam = False
59
+ track.rate = 250
60
+ track.rpm = 300
61
+ track.secs = spt
62
+ track.sz = [2]
63
+ if spt <= 9:
64
+ track.gap3 = 84
65
+ track.cskew = 4
66
+ track.hskew = 2
67
+ elif spt == 10:
68
+ track.gap3 = 30
69
+ else:
70
+ track.gap3 = 3
71
+ track.rate = 261
72
+ track.finalise()
73
+ t = track.mk_track(cyl, head)
74
+
75
+ for n,s in enumerate(t.sectors):
76
+ s.crc = s.idam.crc = s.dam.crc = 0
77
+ s.idam.c, s.idam.h, s.idam.r, s.idam.n = cyl, head, n+1, 2
78
+ s.dam.data = tdat[n*512:(n+1)*512]
79
+
80
+ self.to_track[cyl, head] = t
81
+
82
+
83
+ def get_track(self, cyl: int, side: int) -> Optional[ibm.IBMTrack_Fixed]:
84
+ if (cyl,side) not in self.to_track:
85
+ return None
86
+ return self.to_track[cyl,side]
87
+
88
+
89
+ def emit_track(self, cyl: int, side: int, track) -> None:
90
+ self.to_track[cyl,side] = track
91
+
92
+
93
+ def get_image(self) -> bytes:
94
+
95
+ n_side = max(self.to_track.keys(), default=(0,0), key=lambda x:x[1])[1]
96
+ n_side += 1
97
+ st = min(self.to_track.keys(), default=(0,), key=lambda x:x[0])[0]
98
+ et = max(self.to_track.keys(), default=(0,), key=lambda x:x[0])[0]
99
+
100
+ dat = bytearray()
101
+ spt = None
102
+
103
+ for c in range(st,et+1):
104
+ for h in range(n_side):
105
+
106
+ # Grab the IBM MFM track data. Perform sanity checks.
107
+ error.check((c,h) in self.to_track,
108
+ f'MSA: Missing track {c}.{h} in output')
109
+ track = self.to_track[c,h]
110
+ error.check(isinstance(track, ibm.IBMTrack_Fixed),
111
+ f'MSA: Track {c}.{h} is not an IBM track: '
112
+ 'Maybe missing --format= option?')
113
+ tdat = track.get_img_track()
114
+ if spt is None:
115
+ spt = len(track.sectors)
116
+ error.check(spt == len(track.sectors),
117
+ f'MSA: Track {c}.{h} has incorrect sectors per '
118
+ f'track ({spt} != {len(track.sectors)})')
119
+
120
+ # Compress the track data.
121
+ td = bytearray()
122
+ tidx, runlen, runbyte = 0, 0, 0
123
+ while tidx < len(tdat):
124
+ b, tidx = tdat[tidx], tidx+1
125
+ if b != runbyte:
126
+ if runlen < 4 and runbyte != 0xe5:
127
+ td += bytes([runbyte]) * runlen
128
+ elif runlen != 0:
129
+ td += struct.pack('>2BH', 0xe5, runbyte, runlen)
130
+ runlen = 0
131
+ runbyte = b
132
+ runlen += 1
133
+ if runlen < 4 and runbyte != 0xe5:
134
+ td += bytes([runbyte]) * runlen
135
+ elif runlen != 0:
136
+ td += struct.pack('>2BH', 0xe5, runbyte, runlen)
137
+
138
+ # Use the compressed data if it's shorter than raw data.
139
+ if len(td) < len(tdat):
140
+ dat += struct.pack('>H', len(td))
141
+ dat += td
142
+ else:
143
+ dat += struct.pack('>H', len(tdat))
144
+ dat += tdat
145
+
146
+ header = struct.pack('>2s4H', b'\x0e\x0f', spt, n_side-1, st, et)
147
+
148
+ return header + dat
149
+
150
+
151
+ # Local variables:
152
+ # python-indent: 4
153
+ # End:
@@ -0,0 +1,113 @@
1
+ # greaseweazle/image/nfd.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
+
8
+ from typing import Dict, Tuple, Optional, List
9
+
10
+ import struct
11
+
12
+ from diskstack._vendor.greaseweazle import error
13
+ from diskstack._vendor.greaseweazle.codec.ibm import ibm
14
+ from .image import Image, OptDict
15
+
16
+ TrackDict = Dict[Tuple[int, int], ibm.IBMTrack_Fixed]
17
+
18
+
19
+ class NFD(Image):
20
+
21
+ read_only = True
22
+
23
+ def __init__(self, name: str, _fmt):
24
+ self.to_track: TrackDict = dict()
25
+ self.filename = name
26
+
27
+ @classmethod
28
+ def from_file(cls, name: str, _fmt, opts: OptDict) -> Image:
29
+
30
+ nfd = cls(name, _fmt)
31
+ nfd.apply_r_opts(opts)
32
+
33
+ with open(name, 'rb') as f:
34
+ header = struct.unpack('<15sx256sLBB10x', f.read(288))
35
+ file_id, comment_bytes, header_size, write_protect, heads = header
36
+ if file_id == b'T98FDDIMAGE.R1\0':
37
+ raise error.Fatal('NFD: r1 format images not supported')
38
+ if file_id != b'T98FDDIMAGE.R0\0':
39
+ raise error.Fatal('NFD: not a NFD image')
40
+ if heads != 2:
41
+ raise error.Fatal('NFD: heads != 2 not supported')
42
+ comment = comment_bytes.decode('sjis')
43
+ print('NFD: comment:', comment)
44
+ if write_protect != 0:
45
+ print('NFD: disk is write protected')
46
+ for physical_track in range(0, 163):
47
+ secs: List[Tuple[int, int, int, int]] = []
48
+ track = None
49
+ track_mfm = 0
50
+ for _ in range(1, 27):
51
+ sector_header = struct.unpack('<11B5x', f.read(16))
52
+ c, h, r, n, mfm, ddam, status, st0, st1, st2, pda = sector_header
53
+ if c == 0xff:
54
+ continue
55
+ if track is None:
56
+ track_mfm = mfm
57
+ if mfm != 0:
58
+ track = ibm.IBMTrack_FixedDef('ibm.mfm')
59
+ track.rate = 500
60
+ else:
61
+ track = ibm.IBMTrack_FixedDef('ibm.fm')
62
+ track.rate = 250
63
+ track.rpm = 360
64
+ else:
65
+ if track_mfm != mfm:
66
+ raise error.Fatal(
67
+ 'NFD: mixed FM and MFM tracks not supported')
68
+ if ddam != 0:
69
+ raise error.Fatal('NFD: DDAM not supported')
70
+ if status != 0:
71
+ raise error.Fatal(
72
+ f'NFD: Status {status:%02x} not supported')
73
+ if h not in (0, 1):
74
+ raise error.Fatal(f'NFD: Invalid head value {h}')
75
+ if h == 0 and st0 != 0x00:
76
+ raise error.Fatal(f'NFD: ST0 {st0:%02x} not supported')
77
+ if h == 1 and st0 != 0x04:
78
+ raise error.Fatal(f'NFD: ST0 {st0:%02x} not supported')
79
+ if st1 != 0:
80
+ raise error.Fatal(f'NFD: ST1 {st1:%02x} not supported')
81
+ if st2 != 0:
82
+ raise error.Fatal(f'NFD: ST2 {st2:%02x} not supported')
83
+ if pda != 0x90:
84
+ raise error.Fatal(f'NFD: PDA {pda:%02x} not supported')
85
+ secs.append((c, h, r, n))
86
+ if track is not None:
87
+ assert track is not None
88
+ track.secs = len(secs)
89
+ track.sz = [x[3] for x in secs]
90
+ track.finalise()
91
+ t = ibm.IBMTrack_Fixed.from_config(
92
+ track, physical_track // 2, physical_track % 2,
93
+ warn_on_oversize=True)
94
+ for nr, s in enumerate(t.sectors):
95
+ c, h, r, n = secs[nr]
96
+ s.crc = s.idam.crc = s.dam.crc = 0
97
+ s.idam.c, s.idam.h, s.idam.r, s.idam.n = c, h, r, n
98
+ nfd.to_track[physical_track // 2, physical_track % 2] = t
99
+ f.seek(header_size)
100
+ for _, t in nfd.to_track.items():
101
+ for s in t.sectors:
102
+ s.dam.data = f.read(128 << s.idam.n)
103
+
104
+ return nfd
105
+
106
+ def get_track(self, cyl: int, side: int) -> Optional[ibm.IBMTrack_Fixed]:
107
+ if (cyl, side) not in self.to_track:
108
+ return None
109
+ return self.to_track[cyl, side]
110
+
111
+ # Local variables:
112
+ # python-indent: 4
113
+ # End:
@@ -0,0 +1,47 @@
1
+ # greaseweazle/image/nsi.py
2
+ #
3
+ # North Star Image order:
4
+ # 1. All tracks of side 0
5
+ # 2. All tracks of side 1 (reverse order)
6
+ #
7
+ # Written & released by Keir Fraser <keir.xen@gmail.com>
8
+ #
9
+ # This is free and unencumbered software released into the public domain.
10
+ # See the file COPYING for more details, or visit <http://unlicense.org>.
11
+
12
+ import os
13
+
14
+ from diskstack._vendor.greaseweazle import error
15
+ from diskstack._vendor.greaseweazle.image.img import IMG_AutoFormat
16
+
17
+ class NSI(IMG_AutoFormat):
18
+
19
+ @staticmethod
20
+ def format_from_file(name: str) -> str:
21
+ size = os.path.getsize(name)
22
+ if size == 1*35*10*256:
23
+ return 'northstar.fm.ss'
24
+ # if size == 2*35*10*256:
25
+ # return 'northstar.fm.ds'
26
+ if size == 1*35*10*512:
27
+ return 'northstar.mfm.ss'
28
+ if size == 2*35*10*512:
29
+ return 'northstar.mfm.ds'
30
+ raise error.Fatal(f'NSI: {name}: unrecognised file size')
31
+
32
+ def track_list(self):
33
+ t, l = self.fmt.tracks, []
34
+ prepend = False
35
+ for h in t.heads:
36
+ _l = []
37
+ for c in t.cyls:
38
+ _l.append((c,h))
39
+ if (h & 1) == 1:
40
+ _l.reverse()
41
+ l += _l
42
+ return l
43
+
44
+
45
+ # Local variables:
46
+ # python-indent: 4
47
+ # End: