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
diskstack/__init__.py
ADDED
|
File without changes
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
This is free and unencumbered software released into the public domain.
|
|
2
|
+
|
|
3
|
+
Anyone is free to copy, modify, publish, use, compile, sell, or
|
|
4
|
+
distribute this software, either in source code form or as a compiled
|
|
5
|
+
binary, for any purpose, commercial or non-commercial, and by any
|
|
6
|
+
means.
|
|
7
|
+
|
|
8
|
+
In jurisdictions that recognize copyright laws, the author or authors
|
|
9
|
+
of this software dedicate any and all copyright interest in the
|
|
10
|
+
software to the public domain. We make this dedication for the benefit
|
|
11
|
+
of the public at large and to the detriment of our heirs and
|
|
12
|
+
successors. We intend this dedication to be an overt act of
|
|
13
|
+
relinquishment in perpetuity of all present and future rights to this
|
|
14
|
+
software under copyright law.
|
|
15
|
+
|
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
|
19
|
+
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
|
20
|
+
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
|
21
|
+
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
|
23
|
+
|
|
24
|
+
For more information, please refer to <http://unlicense.org/>
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# greaseweazle/codec/amiga/amigados.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 List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import struct
|
|
11
|
+
import itertools as it
|
|
12
|
+
from bitarray import bitarray
|
|
13
|
+
|
|
14
|
+
from diskstack._vendor.greaseweazle import error
|
|
15
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
16
|
+
from diskstack._vendor.greaseweazle.codec.ibm import ibm
|
|
17
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
|
|
18
|
+
from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
|
|
19
|
+
|
|
20
|
+
default_revs = 1.1
|
|
21
|
+
|
|
22
|
+
sync_bytes = b'\x44\x89\x44\x89'
|
|
23
|
+
sync = bitarray(endian='big')
|
|
24
|
+
sync.frombytes(sync_bytes)
|
|
25
|
+
|
|
26
|
+
bad_sector = b'-=[BAD SECTOR]=-' * 32
|
|
27
|
+
|
|
28
|
+
class AmigaDOS(codec.Codec):
|
|
29
|
+
|
|
30
|
+
# Subclasses must define these
|
|
31
|
+
nsec: int
|
|
32
|
+
clock: float
|
|
33
|
+
|
|
34
|
+
time_per_rev = 0.2
|
|
35
|
+
|
|
36
|
+
verify_revs = default_revs
|
|
37
|
+
|
|
38
|
+
def __init__(self, cyl: int, head: int):
|
|
39
|
+
self.tracknr = cyl*2 + head
|
|
40
|
+
self.sector: List[Optional[Tuple[bytes,bytes]]]
|
|
41
|
+
self.sector = [None] * self.nsec
|
|
42
|
+
self.map: List[Optional[int]]
|
|
43
|
+
self.map = [None] * self.nsec
|
|
44
|
+
|
|
45
|
+
def summary_string(self) -> str:
|
|
46
|
+
nsec, nbad = self.nsec, self.nr_missing()
|
|
47
|
+
s = "AmigaDOS (%d/%d sectors)" % (nsec - nbad, nsec)
|
|
48
|
+
return s
|
|
49
|
+
|
|
50
|
+
# private
|
|
51
|
+
def exists(self, sec_id, togo) -> bool:
|
|
52
|
+
return ((self.sector[sec_id] is not None)
|
|
53
|
+
or (self.map[self.nsec-togo] is not None))
|
|
54
|
+
|
|
55
|
+
# private
|
|
56
|
+
def add(self, sec_id, togo, label, data) -> None:
|
|
57
|
+
assert not self.exists(sec_id, togo)
|
|
58
|
+
self.sector[sec_id] = label, data
|
|
59
|
+
self.map[self.nsec-togo] = sec_id
|
|
60
|
+
|
|
61
|
+
def has_sec(self, sec_id: int) -> bool:
|
|
62
|
+
return self.sector[sec_id] is not None
|
|
63
|
+
|
|
64
|
+
def nr_missing(self) -> int:
|
|
65
|
+
return len([sec for sec in self.sector if sec is None])
|
|
66
|
+
|
|
67
|
+
def get_img_track(self) -> bytearray:
|
|
68
|
+
tdat = bytearray()
|
|
69
|
+
for sec in self.sector:
|
|
70
|
+
tdat += sec[1] if sec is not None else bad_sector
|
|
71
|
+
return tdat
|
|
72
|
+
|
|
73
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
74
|
+
totsize = self.nsec * 512
|
|
75
|
+
if len(tdat) < totsize:
|
|
76
|
+
tdat += bytes(totsize - len(tdat))
|
|
77
|
+
self.map = list(range(self.nsec))
|
|
78
|
+
for sec in range(self.nsec):
|
|
79
|
+
self.sector[sec] = bytes(16), tdat[sec*512:(sec+1)*512]
|
|
80
|
+
return totsize
|
|
81
|
+
|
|
82
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
83
|
+
raw = PLLTrack(time_per_rev = self.time_per_rev,
|
|
84
|
+
clock = self.clock, data = track, pll = pll)
|
|
85
|
+
bits, _ = raw.get_all_data()
|
|
86
|
+
|
|
87
|
+
for offs in bits.search(sync):
|
|
88
|
+
|
|
89
|
+
if self.nr_missing() == 0:
|
|
90
|
+
break
|
|
91
|
+
|
|
92
|
+
sec = bits[offs:offs+544*16].tobytes()
|
|
93
|
+
if len(sec) != 1088:
|
|
94
|
+
continue
|
|
95
|
+
|
|
96
|
+
header = decode(sec[4:12])
|
|
97
|
+
format, track, sec_id, togo = tuple(header)
|
|
98
|
+
if format != 0xff or track != self.tracknr \
|
|
99
|
+
or not(sec_id < self.nsec and 0 < togo <= self.nsec) \
|
|
100
|
+
or self.exists(sec_id, togo):
|
|
101
|
+
continue
|
|
102
|
+
|
|
103
|
+
label = decode(sec[12:44])
|
|
104
|
+
hsum, = struct.unpack('>I', decode(sec[44:52]))
|
|
105
|
+
if hsum != checksum(header + label):
|
|
106
|
+
continue
|
|
107
|
+
|
|
108
|
+
dsum, = struct.unpack('>I', decode(sec[52:60]))
|
|
109
|
+
data = decode(sec[60:1084])
|
|
110
|
+
gap = decode(sec[1084:1088])
|
|
111
|
+
if dsum != checksum(data):
|
|
112
|
+
continue;
|
|
113
|
+
|
|
114
|
+
self.add(sec_id, togo, label, data)
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def master_track(self) -> MasterTrack:
|
|
118
|
+
|
|
119
|
+
# List of sector IDs missing from the sector map:
|
|
120
|
+
missing = iter([x for x in range(self.nsec) if not x in self.map])
|
|
121
|
+
# Sector map with the missing entries filled in:
|
|
122
|
+
full_map = [next(missing) if x is None else x for x in self.map]
|
|
123
|
+
|
|
124
|
+
# Post-index track gap.
|
|
125
|
+
t = encode(bytes(128 * (self.nsec//11)))
|
|
126
|
+
|
|
127
|
+
for nr, sec_id in zip(range(self.nsec), full_map):
|
|
128
|
+
sector = self.sector[sec_id]
|
|
129
|
+
label, data = (bytes(16), bad_sector) if sector is None else sector
|
|
130
|
+
header = bytes([0xff, self.tracknr, sec_id, self.nsec-nr])
|
|
131
|
+
t += sync_bytes
|
|
132
|
+
t += encode(header)
|
|
133
|
+
t += encode(label)
|
|
134
|
+
t += encode(struct.pack('>I', checksum(header + label)))
|
|
135
|
+
t += encode(struct.pack('>I', checksum(data)))
|
|
136
|
+
t += encode(data)
|
|
137
|
+
t += encode(bytes(2))
|
|
138
|
+
|
|
139
|
+
# Add the pre-index gap.
|
|
140
|
+
tlen = (int((self.time_per_rev / self.clock)) + 31) & ~31
|
|
141
|
+
t += bytes(tlen//8-len(t))
|
|
142
|
+
|
|
143
|
+
track = MasterTrack(
|
|
144
|
+
bits = ibm.mfm_encode(t),
|
|
145
|
+
time_per_rev = self.time_per_rev)
|
|
146
|
+
track.verify = self
|
|
147
|
+
return track
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def verify_track(self, flux):
|
|
151
|
+
cyl = self.tracknr // 2
|
|
152
|
+
head = self.tracknr & 1
|
|
153
|
+
readback_track = self.__class__(cyl, head)
|
|
154
|
+
readback_track.decode_flux(flux)
|
|
155
|
+
return (readback_track.nr_missing() == 0
|
|
156
|
+
and self.sector == readback_track.sector)
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
class AmigaDOS_DD(AmigaDOS):
|
|
160
|
+
nsec = 11
|
|
161
|
+
clock = 14/7093790
|
|
162
|
+
|
|
163
|
+
class AmigaDOS_HD(AmigaDOS):
|
|
164
|
+
nsec = 22
|
|
165
|
+
clock = AmigaDOS_DD.clock / 2
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
class AmigaDOSDef(codec.TrackDef):
|
|
169
|
+
|
|
170
|
+
default_revs = default_revs
|
|
171
|
+
|
|
172
|
+
def __init__(self, format_name: str):
|
|
173
|
+
self.secs = None
|
|
174
|
+
self.finalised = False
|
|
175
|
+
|
|
176
|
+
def add_param(self, key: str, val) -> None:
|
|
177
|
+
if key == 'secs':
|
|
178
|
+
val = int(val)
|
|
179
|
+
error.check(val in [11, 22], '%s out of range' % key)
|
|
180
|
+
self.secs = val
|
|
181
|
+
else:
|
|
182
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
183
|
+
|
|
184
|
+
def finalise(self) -> None:
|
|
185
|
+
if self.finalised:
|
|
186
|
+
return
|
|
187
|
+
error.check(self.secs is not None,
|
|
188
|
+
'number of sectors not specified')
|
|
189
|
+
self.finalised = True
|
|
190
|
+
|
|
191
|
+
def mk_track(self, cyl: int, head: int) -> AmigaDOS:
|
|
192
|
+
t: AmigaDOS
|
|
193
|
+
if self.secs == 11:
|
|
194
|
+
t = AmigaDOS_DD(cyl, head)
|
|
195
|
+
else:
|
|
196
|
+
t = AmigaDOS_HD(cyl, head)
|
|
197
|
+
return t
|
|
198
|
+
|
|
199
|
+
|
|
200
|
+
def encode(dat):
|
|
201
|
+
return bytes(it.chain(map(lambda x: (x >> 1) & 0x55, dat),
|
|
202
|
+
map(lambda x: x & 0x55, dat)))
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
def decode(dat):
|
|
206
|
+
length = len(dat)//2
|
|
207
|
+
return bytes(map(lambda x, y: (x << 1 & 0xaa) | (y & 0x55),
|
|
208
|
+
it.islice(dat, 0, length),
|
|
209
|
+
it.islice(dat, length, None)))
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def checksum(dat):
|
|
213
|
+
csum = 0
|
|
214
|
+
for i in range(0, len(dat), 4):
|
|
215
|
+
csum ^= struct.unpack('>I', dat[i:i+4])[0]
|
|
216
|
+
return (csum ^ (csum>>1)) & 0x55555555
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
# Local variables:
|
|
220
|
+
# python-indent: 4
|
|
221
|
+
# End:
|
|
File without changes
|
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# greaseweazle/codec/apple2/apple2_gcr.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 List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import struct
|
|
11
|
+
from bitarray import bitarray
|
|
12
|
+
|
|
13
|
+
from diskstack._vendor.greaseweazle import error
|
|
14
|
+
from diskstack._vendor.greaseweazle import optimised
|
|
15
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
16
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
|
|
17
|
+
from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
|
|
18
|
+
|
|
19
|
+
default_revs = 1.1
|
|
20
|
+
|
|
21
|
+
gap4 = bitarray([1], endian='big')
|
|
22
|
+
ff40 = bitarray([1]*8 + [0]*2, endian='big')
|
|
23
|
+
ff32 = ff40[:8]
|
|
24
|
+
|
|
25
|
+
addr_sync = bitarray(endian='big')
|
|
26
|
+
addr_sync.frombytes(b'\xd5\xaa\x96')
|
|
27
|
+
|
|
28
|
+
data_sync = bitarray(endian='big')
|
|
29
|
+
data_sync.frombytes(b'\xd5\xaa\xad')
|
|
30
|
+
|
|
31
|
+
trailer = bitarray(endian='big')
|
|
32
|
+
trailer.frombytes(b'\xde\xaa\xeb')
|
|
33
|
+
|
|
34
|
+
bad_sector = b'-=[BAD SECTOR]=-' * 16
|
|
35
|
+
|
|
36
|
+
class Apple2GCR(codec.Codec):
|
|
37
|
+
|
|
38
|
+
time_per_rev = 0.2
|
|
39
|
+
|
|
40
|
+
verify_revs = default_revs
|
|
41
|
+
|
|
42
|
+
def __init__(self, cyl: int, head: int, config):
|
|
43
|
+
self.cyl, self.head = cyl, head
|
|
44
|
+
self.config = config
|
|
45
|
+
self.clock = config.clock
|
|
46
|
+
self.sector: List[Optional[bytes]]
|
|
47
|
+
self.sector = [None] * self.nsec
|
|
48
|
+
self.vol_id: Optional[int] = None
|
|
49
|
+
error.check(optimised.enabled,
|
|
50
|
+
'Apple2 GCR requires optimised C extension')
|
|
51
|
+
|
|
52
|
+
@property
|
|
53
|
+
def nsec(self) -> int:
|
|
54
|
+
return len(self.config.secs)
|
|
55
|
+
|
|
56
|
+
def summary_string(self) -> str:
|
|
57
|
+
nsec, nbad = self.nsec, self.nr_missing()
|
|
58
|
+
s = "Apple2 GCR (%d/%d sectors)" % (nsec - nbad, nsec)
|
|
59
|
+
return s
|
|
60
|
+
|
|
61
|
+
def set_vol_id(self, vol_id: int):
|
|
62
|
+
assert self.vol_id is None
|
|
63
|
+
self.vol_id = vol_id
|
|
64
|
+
|
|
65
|
+
# private
|
|
66
|
+
def add(self, sec_id, data) -> None:
|
|
67
|
+
assert not self.has_sec(sec_id)
|
|
68
|
+
self.sector[sec_id] = data
|
|
69
|
+
|
|
70
|
+
# private
|
|
71
|
+
def tracknr(self) -> int:
|
|
72
|
+
return self.cyl
|
|
73
|
+
|
|
74
|
+
def has_sec(self, sec_id: int) -> bool:
|
|
75
|
+
return self.sector[sec_id] is not None
|
|
76
|
+
|
|
77
|
+
def nr_missing(self) -> int:
|
|
78
|
+
return len([sec for sec in self.sector if sec is None])
|
|
79
|
+
|
|
80
|
+
def get_img_track(self) -> bytearray:
|
|
81
|
+
tdat = bytearray()
|
|
82
|
+
for i in self.config.secs:
|
|
83
|
+
sec = self.sector[i]
|
|
84
|
+
tdat += sec if sec is not None else bad_sector
|
|
85
|
+
return tdat
|
|
86
|
+
|
|
87
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
88
|
+
totsize = self.nsec * 256
|
|
89
|
+
if len(tdat) < totsize:
|
|
90
|
+
tdat += bytes(totsize - len(tdat))
|
|
91
|
+
for i,sec in enumerate(self.config.secs):
|
|
92
|
+
self.sector[sec] = tdat[i*256:(i+1)*256]
|
|
93
|
+
return totsize
|
|
94
|
+
|
|
95
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
96
|
+
raw = PLLTrack(time_per_rev = self.time_per_rev,
|
|
97
|
+
clock = self.clock, data = track, pll = pll,
|
|
98
|
+
lowpass_thresh = 2.5e-6)
|
|
99
|
+
bits, _ = raw.get_all_data()
|
|
100
|
+
|
|
101
|
+
for offs in bits.search(addr_sync):
|
|
102
|
+
|
|
103
|
+
if self.nr_missing() == 0:
|
|
104
|
+
break
|
|
105
|
+
|
|
106
|
+
# Decode header
|
|
107
|
+
offs += 3*8
|
|
108
|
+
sec = bits[offs:offs+8*8].tobytes()
|
|
109
|
+
if len(sec) != 8:
|
|
110
|
+
continue
|
|
111
|
+
hdr = map(lambda x: (x & x>>7) & 255,
|
|
112
|
+
list(struct.unpack('>4H', sec)))
|
|
113
|
+
vol_id, trk_id, sec_id, csum = tuple(hdr)
|
|
114
|
+
|
|
115
|
+
# Validate header
|
|
116
|
+
if csum != vol_id ^ trk_id ^ sec_id:
|
|
117
|
+
continue
|
|
118
|
+
if (trk_id != self.tracknr() or sec_id >= self.nsec):
|
|
119
|
+
print('T%d.%d: Ignoring unexpected sector C:%d S:%d ID:%04x'
|
|
120
|
+
% (self.cyl, self.head, trk_id, sec_id, vol_id))
|
|
121
|
+
continue
|
|
122
|
+
if self.vol_id is None:
|
|
123
|
+
self.vol_id = vol_id
|
|
124
|
+
elif self.vol_id != vol_id:
|
|
125
|
+
print('T%d.%d: Expected ID %04x in sector C:%d S:%d ID:%04x'
|
|
126
|
+
% (self.cyl, self.head, self.vol_id,
|
|
127
|
+
trk_id, sec_id, vol_id))
|
|
128
|
+
continue
|
|
129
|
+
if self.has_sec(sec_id):
|
|
130
|
+
continue
|
|
131
|
+
|
|
132
|
+
# Find data
|
|
133
|
+
offs += 8*8
|
|
134
|
+
dat_offs = list(bits[offs:offs+100*8].search(data_sync))
|
|
135
|
+
if len(dat_offs) != 1:
|
|
136
|
+
continue
|
|
137
|
+
offs += dat_offs[0]
|
|
138
|
+
|
|
139
|
+
# Decode data
|
|
140
|
+
sec, csum = optimised.decode_apple2_sector(
|
|
141
|
+
bits[offs+3*8:offs+400*8].tobytes())
|
|
142
|
+
if csum != 0:
|
|
143
|
+
continue
|
|
144
|
+
|
|
145
|
+
self.add(sec_id, sec)
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
def master_track(self) -> MasterTrack:
|
|
149
|
+
|
|
150
|
+
vol_id = self.vol_id if self.vol_id is not None else 254
|
|
151
|
+
trk_id = self.tracknr()
|
|
152
|
+
|
|
153
|
+
# Post-index track gap.
|
|
154
|
+
t = gap4 * 300
|
|
155
|
+
|
|
156
|
+
for sec_id in range(self.nsec):
|
|
157
|
+
sector = self.sector[sec_id]
|
|
158
|
+
data = bad_sector if sector is None else sector
|
|
159
|
+
t += ff40*18 + ff32 + addr_sync
|
|
160
|
+
t.frombytes(b''.join(map(
|
|
161
|
+
lambda x: bytes([x>>1 | 0xaa, x | 0xaa]),
|
|
162
|
+
[vol_id, trk_id, sec_id, vol_id ^ trk_id ^ sec_id])))
|
|
163
|
+
t += trailer + ff40*6 + data_sync
|
|
164
|
+
t.frombytes(optimised.encode_apple2_sector(data))
|
|
165
|
+
t += trailer
|
|
166
|
+
|
|
167
|
+
# Add the pre-index gap.
|
|
168
|
+
tlen = int((self.time_per_rev / self.clock))
|
|
169
|
+
t += gap4 * (tlen-len(t))
|
|
170
|
+
|
|
171
|
+
track = MasterTrack(bits = t, time_per_rev = self.time_per_rev)
|
|
172
|
+
track.verify = self
|
|
173
|
+
return track
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def verify_track(self, flux):
|
|
177
|
+
readback_track = self.__class__(self.cyl, self.head, self.config)
|
|
178
|
+
readback_track.decode_flux(flux)
|
|
179
|
+
return (readback_track.nr_missing() == 0
|
|
180
|
+
and self.sector == readback_track.sector)
|
|
181
|
+
|
|
182
|
+
|
|
183
|
+
class Apple2GCRDef(codec.TrackDef):
|
|
184
|
+
|
|
185
|
+
default_revs = default_revs
|
|
186
|
+
|
|
187
|
+
def __init__(self, format_name: str):
|
|
188
|
+
self.secs: List[int] = []
|
|
189
|
+
self.clock: Optional[float] = None
|
|
190
|
+
self.finalised = False
|
|
191
|
+
|
|
192
|
+
def add_param(self, key: str, val) -> None:
|
|
193
|
+
if key == 'secs':
|
|
194
|
+
self.secs = []
|
|
195
|
+
for x in val.split(','):
|
|
196
|
+
self.secs.append(int(x))
|
|
197
|
+
rsecs = [-1] * len(self.secs)
|
|
198
|
+
for i,x in enumerate(self.secs):
|
|
199
|
+
error.check(x < len(rsecs), f'sector {x} is out of range')
|
|
200
|
+
error.check(rsecs[x] == -1, f'sector {x} is repeated')
|
|
201
|
+
rsecs[x] = i
|
|
202
|
+
elif key == 'clock':
|
|
203
|
+
val = float(val)
|
|
204
|
+
self.clock = val * 1e-6
|
|
205
|
+
else:
|
|
206
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
207
|
+
|
|
208
|
+
def finalise(self) -> None:
|
|
209
|
+
if self.finalised:
|
|
210
|
+
return
|
|
211
|
+
error.check(self.secs,
|
|
212
|
+
'sector list not specified')
|
|
213
|
+
error.check(self.clock is not None,
|
|
214
|
+
'clock period not specified')
|
|
215
|
+
self.finalised = True
|
|
216
|
+
|
|
217
|
+
def mk_track(self, cyl: int, head: int) -> Apple2GCR:
|
|
218
|
+
return Apple2GCR(cyl, head, self)
|
|
219
|
+
|
|
220
|
+
|
|
221
|
+
# Local variables:
|
|
222
|
+
# python-indent: 4
|
|
223
|
+
# End:
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# greaseweazle/codec/bitcell.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 List, Optional, Tuple
|
|
9
|
+
|
|
10
|
+
import struct
|
|
11
|
+
from bitarray import bitarray
|
|
12
|
+
|
|
13
|
+
from diskstack._vendor.greaseweazle import error
|
|
14
|
+
from diskstack._vendor.greaseweazle import optimised
|
|
15
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
16
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
|
|
17
|
+
from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
|
|
18
|
+
|
|
19
|
+
default_revs = 1
|
|
20
|
+
|
|
21
|
+
class BitcellTrack(codec.Codec):
|
|
22
|
+
|
|
23
|
+
nsec = 0
|
|
24
|
+
|
|
25
|
+
def __init__(self, cyl: int, head: int, config):
|
|
26
|
+
self.cyl, self.head = cyl, head
|
|
27
|
+
self.config = config
|
|
28
|
+
self.clock = config.clock
|
|
29
|
+
self.raw: Optional[PLLTrack] = None
|
|
30
|
+
|
|
31
|
+
@property
|
|
32
|
+
def time_per_rev(self) -> float:
|
|
33
|
+
if self.raw is not None:
|
|
34
|
+
return self.raw.time_per_rev
|
|
35
|
+
if self.config.time_per_rev is not None:
|
|
36
|
+
return self.config.time_per_rev
|
|
37
|
+
return 0.2
|
|
38
|
+
|
|
39
|
+
def summary_string(self) -> str:
|
|
40
|
+
if self.raw is None:
|
|
41
|
+
s = "Raw Bitcell (empty)"
|
|
42
|
+
else:
|
|
43
|
+
bits, _ = self.raw.get_revolution(0)
|
|
44
|
+
s = ("Raw Bitcell (%d bits, %.2fms)"
|
|
45
|
+
% (len(bits), self.raw.time_per_rev*1000))
|
|
46
|
+
return s
|
|
47
|
+
|
|
48
|
+
def has_sec(self, sec_id: int) -> bool:
|
|
49
|
+
return False
|
|
50
|
+
|
|
51
|
+
def nr_missing(self) -> int:
|
|
52
|
+
return 0
|
|
53
|
+
|
|
54
|
+
def get_img_track(self) -> bytearray:
|
|
55
|
+
return bytearray()
|
|
56
|
+
|
|
57
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
58
|
+
return 0
|
|
59
|
+
|
|
60
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
61
|
+
flux = track.flux()
|
|
62
|
+
flux.cue_at_index()
|
|
63
|
+
time_per_rev = self.config.time_per_rev
|
|
64
|
+
if time_per_rev is None:
|
|
65
|
+
time_per_rev = flux.time_per_rev
|
|
66
|
+
self.raw = PLLTrack(time_per_rev = time_per_rev,
|
|
67
|
+
clock = self.clock, data = flux, pll = pll)
|
|
68
|
+
|
|
69
|
+
def master_track(self) -> MasterTrack:
|
|
70
|
+
if self.raw is None:
|
|
71
|
+
nbytes = int(self.time_per_rev / self.clock) // 8
|
|
72
|
+
track = MasterTrack(bits = bytes(nbytes),
|
|
73
|
+
time_per_rev = self.time_per_rev,
|
|
74
|
+
weak = [(0,nbytes*8)])
|
|
75
|
+
track.force_random_weak = True
|
|
76
|
+
return track
|
|
77
|
+
bits, _ = self.raw.get_revolution(0)
|
|
78
|
+
track = MasterTrack(bits = bits, time_per_rev = self.time_per_rev)
|
|
79
|
+
return track
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class BitcellTrackDef(codec.TrackDef):
|
|
83
|
+
|
|
84
|
+
default_revs = default_revs
|
|
85
|
+
|
|
86
|
+
def __init__(self, format_name: str):
|
|
87
|
+
self.clock: Optional[float] = None
|
|
88
|
+
self.time_per_rev: Optional[float] = None
|
|
89
|
+
self.finalised = False
|
|
90
|
+
|
|
91
|
+
def add_param(self, key: str, val) -> None:
|
|
92
|
+
if key == 'secs':
|
|
93
|
+
val = int(val)
|
|
94
|
+
self.secs = val
|
|
95
|
+
elif key == 'clock':
|
|
96
|
+
val = float(val)
|
|
97
|
+
self.clock = val * 1e-6
|
|
98
|
+
elif key == 'time_per_rev':
|
|
99
|
+
val = float(val)
|
|
100
|
+
self.time_per_rev = val
|
|
101
|
+
else:
|
|
102
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
103
|
+
|
|
104
|
+
def finalise(self) -> None:
|
|
105
|
+
if self.finalised:
|
|
106
|
+
return
|
|
107
|
+
error.check(self.clock is not None,
|
|
108
|
+
'clock period not specified')
|
|
109
|
+
self.finalised = True
|
|
110
|
+
|
|
111
|
+
def mk_track(self, cyl: int, head: int) -> BitcellTrack:
|
|
112
|
+
return BitcellTrack(cyl, head, self)
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
# Local variables:
|
|
116
|
+
# python-indent: 4
|
|
117
|
+
# End:
|