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.
- diskstack/__init__.py +5 -0
- diskstack/_vendor/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/COPYING +24 -0
- diskstack/_vendor/greaseweazle/__init__.py +2 -0
- diskstack/_vendor/greaseweazle/codec/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/amiga/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/amiga/amigados.py +221 -0
- diskstack/_vendor/greaseweazle/codec/apple2/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/apple2/apple2_gcr.py +223 -0
- diskstack/_vendor/greaseweazle/codec/bitcell.py +117 -0
- diskstack/_vendor/greaseweazle/codec/codec.py +362 -0
- diskstack/_vendor/greaseweazle/codec/commodore/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/commodore/c64_gcr.py +229 -0
- diskstack/_vendor/greaseweazle/codec/datageneral/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/datageneral/datageneral.py +211 -0
- diskstack/_vendor/greaseweazle/codec/hp/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/hp/hp_mmfm.py +258 -0
- diskstack/_vendor/greaseweazle/codec/ibm/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/ibm/ibm.py +1005 -0
- diskstack/_vendor/greaseweazle/codec/macintosh/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/macintosh/mac_gcr.py +238 -0
- diskstack/_vendor/greaseweazle/codec/micropolis/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/micropolis/micropolis.py +233 -0
- diskstack/_vendor/greaseweazle/codec/northstar/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/codec/northstar/northstar.py +210 -0
- diskstack/_vendor/greaseweazle/data/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/data/diskdefs.cfg +81 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_acorn.cfg +119 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_akai.cfg +27 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_amiga.cfg +17 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_apple2.cfg +31 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_apricot.cfg +25 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_atari.cfg +33 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_atarist.cfg +76 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_coco.cfg +98 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_commodore.cfg +127 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_datageneral.cfg +8 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_dec.cfg +37 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_dragon.cfg +61 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_eagle.cfg +25 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_ensoniq.cfg +39 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_epson.cfg +95 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_gem.cfg +15 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_hp.cfg +25 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_ibm.cfg +140 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_kaypro.cfg +58 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_luxor.cfg +96 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_mac.cfg +71 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_micropolis.cfg +73 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_mm1.cfg +69 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_msx.cfg +45 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_northstar.cfg +37 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_occ1.cfg +23 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_olivetti.cfg +18 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_pc98.cfg +69 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_raw.cfg +25 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_rm.cfg +98 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_sci.cfg +15 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_sega.cfg +13 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_sharp.cfg +13 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_thomson.cfg +78 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_tsc.cfg +81 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_xerox.cfg +59 -0
- diskstack/_vendor/greaseweazle/data/diskdefs_zx.cfg +245 -0
- diskstack/_vendor/greaseweazle/error.py +19 -0
- diskstack/_vendor/greaseweazle/flux.py +315 -0
- diskstack/_vendor/greaseweazle/image/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/image/a2r.py +133 -0
- diskstack/_vendor/greaseweazle/image/acorn.py +28 -0
- diskstack/_vendor/greaseweazle/image/adf.py +15 -0
- diskstack/_vendor/greaseweazle/image/apple2.py +18 -0
- diskstack/_vendor/greaseweazle/image/apridisk.py +107 -0
- diskstack/_vendor/greaseweazle/image/caps.py +464 -0
- diskstack/_vendor/greaseweazle/image/d64.py +51 -0
- diskstack/_vendor/greaseweazle/image/d81.py +28 -0
- diskstack/_vendor/greaseweazle/image/d88.py +211 -0
- diskstack/_vendor/greaseweazle/image/dcp.py +45 -0
- diskstack/_vendor/greaseweazle/image/dim.py +53 -0
- diskstack/_vendor/greaseweazle/image/dmk.py +166 -0
- diskstack/_vendor/greaseweazle/image/dsk.py +31 -0
- diskstack/_vendor/greaseweazle/image/edsk.py +591 -0
- diskstack/_vendor/greaseweazle/image/fd.py +23 -0
- diskstack/_vendor/greaseweazle/image/fdi.py +41 -0
- diskstack/_vendor/greaseweazle/image/hdm.py +15 -0
- diskstack/_vendor/greaseweazle/image/hfe.py +742 -0
- diskstack/_vendor/greaseweazle/image/image.py +130 -0
- diskstack/_vendor/greaseweazle/image/imd.py +201 -0
- diskstack/_vendor/greaseweazle/image/img.py +111 -0
- diskstack/_vendor/greaseweazle/image/kryoflux.py +304 -0
- diskstack/_vendor/greaseweazle/image/mgt.py +15 -0
- diskstack/_vendor/greaseweazle/image/msa.py +153 -0
- diskstack/_vendor/greaseweazle/image/nfd.py +113 -0
- diskstack/_vendor/greaseweazle/image/nsi.py +47 -0
- diskstack/_vendor/greaseweazle/image/scp.py +471 -0
- diskstack/_vendor/greaseweazle/image/sf7.py +15 -0
- diskstack/_vendor/greaseweazle/image/sharp2d.py +15 -0
- diskstack/_vendor/greaseweazle/image/td0.py +141 -0
- diskstack/_vendor/greaseweazle/image/xdf.py +15 -0
- diskstack/_vendor/greaseweazle/optimised/__init__.py +22 -0
- diskstack/_vendor/greaseweazle/tools/__init__.py +0 -0
- diskstack/_vendor/greaseweazle/tools/util.py +133 -0
- diskstack/_vendor/greaseweazle/track.py +494 -0
- diskstack/cache.py +210 -0
- diskstack/candidates.py +396 -0
- diskstack/cli.py +348 -0
- diskstack/errors.py +5 -0
- diskstack/filler.py +29 -0
- diskstack/formats.py +381 -0
- diskstack/parallel.py +73 -0
- diskstack/report.py +353 -0
- diskstack/stack.py +317 -0
- diskstack-1.0.0.dist-info/METADATA +329 -0
- diskstack-1.0.0.dist-info/RECORD +119 -0
- diskstack-1.0.0.dist-info/WHEEL +5 -0
- diskstack-1.0.0.dist-info/entry_points.txt +2 -0
- diskstack-1.0.0.dist-info/licenses/LICENSE +21 -0
- diskstack-1.0.0.dist-info/licenses/NOTICE +55 -0
- diskstack-1.0.0.dist-info/licenses/diskstack/_vendor/greaseweazle/COPYING +24 -0
- 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:
|