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
|
File without changes
|
|
@@ -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:
|
|
File without changes
|
|
@@ -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:
|
|
File without changes
|
|
@@ -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
|