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,1005 @@
|
|
|
1
|
+
# greaseweazle/codec/ibm/ibm.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 __future__ import annotations
|
|
9
|
+
from typing import Any, List, Optional, Union, Tuple
|
|
10
|
+
|
|
11
|
+
import re
|
|
12
|
+
import copy, heapq, struct, functools
|
|
13
|
+
import itertools as it
|
|
14
|
+
from bitarray import bitarray
|
|
15
|
+
from enum import Enum
|
|
16
|
+
import crcmod.predefined
|
|
17
|
+
|
|
18
|
+
from diskstack._vendor.greaseweazle import error
|
|
19
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
20
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLL, PLLTrack
|
|
21
|
+
from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
|
|
22
|
+
|
|
23
|
+
default_revs = 2
|
|
24
|
+
|
|
25
|
+
def sync(dat, clk=0xc7):
|
|
26
|
+
x = 0
|
|
27
|
+
for i in range(8):
|
|
28
|
+
x <<= 1
|
|
29
|
+
x |= (clk >> (7-i)) & 1
|
|
30
|
+
x <<= 1
|
|
31
|
+
x |= (dat >> (7-i)) & 1
|
|
32
|
+
return bytes(struct.pack('>H', x))
|
|
33
|
+
|
|
34
|
+
fm_sync_prefix = bitarray(endian='big')
|
|
35
|
+
fm_sync_prefix.frombytes(b'\xaa\xaa' + sync(0xf8))
|
|
36
|
+
fm_sync_prefix = fm_sync_prefix[:16+10]
|
|
37
|
+
|
|
38
|
+
fm_iam_sync_bytes = sync(0xfc, 0xd7)
|
|
39
|
+
fm_iam_sync = bitarray(endian='big')
|
|
40
|
+
fm_iam_sync.frombytes(b'\xaa\xaa' + fm_iam_sync_bytes)
|
|
41
|
+
|
|
42
|
+
mfm_iam_sync_bytes = b'\x52\x24' * 3
|
|
43
|
+
mfm_iam_sync = bitarray(endian='big')
|
|
44
|
+
mfm_iam_sync.frombytes(mfm_iam_sync_bytes)
|
|
45
|
+
|
|
46
|
+
mfm_sync_bytes = b'\x44\x89' * 3
|
|
47
|
+
mfm_sync = bitarray(endian='big')
|
|
48
|
+
mfm_sync.frombytes(mfm_sync_bytes)
|
|
49
|
+
|
|
50
|
+
def fm_encode(dat):
|
|
51
|
+
out = bytearray()
|
|
52
|
+
for x in dat:
|
|
53
|
+
if (x & 0xaa) == 0:
|
|
54
|
+
x |= 0xaa
|
|
55
|
+
out.append(x)
|
|
56
|
+
return bytes(out)
|
|
57
|
+
|
|
58
|
+
def mfm_encode(dat):
|
|
59
|
+
y = 0
|
|
60
|
+
out = bytearray()
|
|
61
|
+
for x in dat:
|
|
62
|
+
y = (y<<8) | x
|
|
63
|
+
if (x & 0xaa) == 0:
|
|
64
|
+
y |= ~((y>>1)|(y<<1)) & 0xaaaa
|
|
65
|
+
y &= 255
|
|
66
|
+
out.append(y)
|
|
67
|
+
return bytes(out)
|
|
68
|
+
|
|
69
|
+
encode_list: List[int] = []
|
|
70
|
+
for x in range(256):
|
|
71
|
+
y = 0
|
|
72
|
+
for i in range(8):
|
|
73
|
+
y <<= 2
|
|
74
|
+
y |= (x >> (7-i)) & 1
|
|
75
|
+
encode_list.append(y)
|
|
76
|
+
|
|
77
|
+
def encode(dat):
|
|
78
|
+
out = bytearray()
|
|
79
|
+
for x in dat:
|
|
80
|
+
out += struct.pack('>H', encode_list[x])
|
|
81
|
+
return bytes(out)
|
|
82
|
+
doubler = encode
|
|
83
|
+
|
|
84
|
+
decode_list = bytearray(0x5556)
|
|
85
|
+
for x in range(0x5556):
|
|
86
|
+
index = x & 0x5555
|
|
87
|
+
y = (index + (index >> 1)) & 0x3333
|
|
88
|
+
y = (y + (y >> 2)) & 0x0F0F
|
|
89
|
+
y = (y + (y >> 4)) & 0x00FF
|
|
90
|
+
decode_list[index] = y
|
|
91
|
+
|
|
92
|
+
def decode(dat):
|
|
93
|
+
out = bytearray()
|
|
94
|
+
for x,y in zip(dat[::2], dat[1::2]):
|
|
95
|
+
out.append(decode_list[((x<<8)|y)&0x5555])
|
|
96
|
+
return bytes(out)
|
|
97
|
+
|
|
98
|
+
crc16 = crcmod.predefined.Crc('crc-ccitt-false')
|
|
99
|
+
|
|
100
|
+
# Create logical sector map in rotational order
|
|
101
|
+
def sec_map(nsec: int, interleave: int, cskew: int, hskew: int,
|
|
102
|
+
cyl: int, head: int) -> List[int]:
|
|
103
|
+
sec_map, pos = [-1] * nsec, 0
|
|
104
|
+
if nsec != 0:
|
|
105
|
+
pos = (cyl*cskew + head*hskew) % nsec
|
|
106
|
+
for i in range(nsec):
|
|
107
|
+
while sec_map[pos] != -1:
|
|
108
|
+
pos = (pos + 1) % nsec
|
|
109
|
+
sec_map[pos] = i
|
|
110
|
+
pos = (pos + interleave) % nsec
|
|
111
|
+
return sec_map
|
|
112
|
+
|
|
113
|
+
def sec_sz(n):
|
|
114
|
+
return 128 << n if n <= 7 else 128 << 8
|
|
115
|
+
|
|
116
|
+
class Gaps:
|
|
117
|
+
def __init__(self, gap1, gap2, gap3, gap4a):
|
|
118
|
+
self.gap1 = gap1 # Post IAM
|
|
119
|
+
self.gap2 = gap2 # Post IDAM
|
|
120
|
+
self.gap3 = gap3 # Post DAM
|
|
121
|
+
self.gap4a = gap4a # Post Index
|
|
122
|
+
|
|
123
|
+
FMGaps = Gaps(
|
|
124
|
+
gap1 = 26,
|
|
125
|
+
gap2 = 11,
|
|
126
|
+
gap3 = [ 27, 42, 58, 138, 255, 255, 255, 255 ],
|
|
127
|
+
gap4a = 40
|
|
128
|
+
)
|
|
129
|
+
|
|
130
|
+
MFMGaps = Gaps(
|
|
131
|
+
gap1 = 50,
|
|
132
|
+
gap2 = 22,
|
|
133
|
+
gap3 = [ 32, 54, 84, 116, 255, 255, 255, 255 ],
|
|
134
|
+
gap4a = 80
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
|
|
138
|
+
class Mark:
|
|
139
|
+
IAM = 0xfc
|
|
140
|
+
IDAM = 0xfe
|
|
141
|
+
DAM = 0xfb
|
|
142
|
+
DDAM = 0xf8
|
|
143
|
+
# DEC RX02
|
|
144
|
+
DAM_DEC_MMFM = 0xfd
|
|
145
|
+
DDAM_DEC_MMFM = 0xf9
|
|
146
|
+
# TRS-80
|
|
147
|
+
DAM_TRS80_DIR = 0xfa
|
|
148
|
+
|
|
149
|
+
class Mode(Enum):
|
|
150
|
+
FM, MFM, DEC_RX02 = range(3)
|
|
151
|
+
def __str__(self):
|
|
152
|
+
NAMES = [ 'IBM FM', 'IBM MFM', 'DEC RX02' ]
|
|
153
|
+
return f'{NAMES[self.value]}'
|
|
154
|
+
|
|
155
|
+
class TrackArea:
|
|
156
|
+
def __init__(self, start, end, crc=None):
|
|
157
|
+
self.start = start
|
|
158
|
+
self.end = end
|
|
159
|
+
self.crc = crc
|
|
160
|
+
def delta(self, delta):
|
|
161
|
+
self.start -= delta
|
|
162
|
+
self.end -= delta
|
|
163
|
+
def __eq__(self, x):
|
|
164
|
+
return (isinstance(x, type(self))
|
|
165
|
+
and abs(self.start - x.start) < 1000
|
|
166
|
+
and abs(self.end - x.end) < 1000
|
|
167
|
+
and self.crc == x.crc)
|
|
168
|
+
|
|
169
|
+
class IDAM(TrackArea):
|
|
170
|
+
def __init__(self, start, end, crc, c, h, r, n):
|
|
171
|
+
super().__init__(start, end, crc)
|
|
172
|
+
self.c = c
|
|
173
|
+
self.h = h
|
|
174
|
+
self.r = r
|
|
175
|
+
self.n = n
|
|
176
|
+
def __str__(self):
|
|
177
|
+
return ("IDAM:%6d-%6d c=%02x h=%02x r=%02x n=%02x CRC:%04x"
|
|
178
|
+
% (self.start, self.end, self.c, self.h, self.r, self.n,
|
|
179
|
+
self.crc))
|
|
180
|
+
def __eq__(self, x):
|
|
181
|
+
return (super().__eq__(x)
|
|
182
|
+
and self.c == x.c and self.h == x.h
|
|
183
|
+
and self.r == x.r and self.n == x.n)
|
|
184
|
+
def __copy__(self):
|
|
185
|
+
return IDAM(self.start, self.end, self.crc,
|
|
186
|
+
self.c, self.h, self.r, self.n)
|
|
187
|
+
|
|
188
|
+
class DAM(TrackArea):
|
|
189
|
+
def __init__(self, start, end, crc, mark, data=None):
|
|
190
|
+
super().__init__(start, end, crc)
|
|
191
|
+
self.mark = mark
|
|
192
|
+
self.data = data
|
|
193
|
+
def __str__(self):
|
|
194
|
+
return "DAM: %6d-%6d mark=%02x" % (self.start, self.end, self.mark)
|
|
195
|
+
def __eq__(self, x):
|
|
196
|
+
return (super().__eq__(x)
|
|
197
|
+
and self.mark == x.mark
|
|
198
|
+
and self.data == x.data)
|
|
199
|
+
def __copy__(self):
|
|
200
|
+
return DAM(self.start, self.end, self.crc, self.mark, self.data)
|
|
201
|
+
|
|
202
|
+
class Sector(TrackArea):
|
|
203
|
+
def __init__(self, idam, dam):
|
|
204
|
+
super().__init__(idam.start, dam.end, idam.crc | dam.crc)
|
|
205
|
+
self.idam = idam
|
|
206
|
+
self.dam = dam
|
|
207
|
+
def __str__(self):
|
|
208
|
+
s = "Sec: %6d-%6d CRC:%04x\n" % (self.start, self.end, self.crc)
|
|
209
|
+
s += " " + str(self.idam) + "\n"
|
|
210
|
+
s += " " + str(self.dam)
|
|
211
|
+
return s
|
|
212
|
+
def delta(self, delta):
|
|
213
|
+
super().delta(delta)
|
|
214
|
+
self.idam.delta(delta)
|
|
215
|
+
self.dam.delta(delta)
|
|
216
|
+
def __eq__(self, x):
|
|
217
|
+
return (super().__eq__(x)
|
|
218
|
+
and self.idam == x.idam
|
|
219
|
+
and self.dam == x.dam)
|
|
220
|
+
|
|
221
|
+
class IAM(TrackArea):
|
|
222
|
+
def __str__(self):
|
|
223
|
+
return "IAM: %6d-%6d" % (self.start, self.end)
|
|
224
|
+
def __copy__(self):
|
|
225
|
+
return IAM(self.start, self.end)
|
|
226
|
+
|
|
227
|
+
|
|
228
|
+
class DEC_MMFM:
|
|
229
|
+
def __init__(self):
|
|
230
|
+
# Encode: 011110 -> 01000100010
|
|
231
|
+
self.encode_search = bitarray('011110', endian='big')
|
|
232
|
+
self.encode_replace = bitarray('01000100010', endian='big')
|
|
233
|
+
# Decode: DCD=000 -> 11
|
|
234
|
+
self.decode_search = bitarray('000', endian='big')
|
|
235
|
+
self.decode_replace = bitarray('101', endian='big')
|
|
236
|
+
sync_prefix = bitarray(endian='big')
|
|
237
|
+
sync_prefix.frombytes(doubler(b'\xaa\xaa' + sync(0xf8)))
|
|
238
|
+
self.sync_prefix = sync_prefix[:len(fm_sync_prefix)*2]
|
|
239
|
+
def encode(self, pre: bytes) -> bytes:
|
|
240
|
+
pre_bits = bitarray(endian='big')
|
|
241
|
+
pre_bits.frombytes(pre)
|
|
242
|
+
post_bits = bitarray(endian='big')
|
|
243
|
+
post_bits.frombytes(mfm_encode(encode(pre)))
|
|
244
|
+
for x in pre_bits.search(self.encode_search):
|
|
245
|
+
post_bits[x*2+1:x*2+12] = self.encode_replace
|
|
246
|
+
return post_bits.tobytes()
|
|
247
|
+
def decode(self, bits: bitarray) -> bytes:
|
|
248
|
+
for x in bits.search(self.decode_search):
|
|
249
|
+
if x&1 != 0: # Only matches starting on a data bit
|
|
250
|
+
bits[x:x+3] = self.decode_replace
|
|
251
|
+
return decode(bits.tobytes())
|
|
252
|
+
|
|
253
|
+
dec_mmfm = DEC_MMFM()
|
|
254
|
+
|
|
255
|
+
class IBMTrack(codec.Codec):
|
|
256
|
+
|
|
257
|
+
# Subclasses must define these
|
|
258
|
+
time_per_rev: float
|
|
259
|
+
clock: float
|
|
260
|
+
|
|
261
|
+
verify_revs: float = 1
|
|
262
|
+
|
|
263
|
+
def __init__(self, cyl: int, head: int, mode: Mode):
|
|
264
|
+
self.cyl, self.head = cyl, head
|
|
265
|
+
self.sectors: List[Sector] = []
|
|
266
|
+
self.iams: List[IAM] = []
|
|
267
|
+
self.mode = mode
|
|
268
|
+
if mode is Mode.FM or mode is Mode.DEC_RX02:
|
|
269
|
+
self.gap_presync = 6
|
|
270
|
+
self.gapbyte = 0xff
|
|
271
|
+
elif mode is Mode.MFM:
|
|
272
|
+
self.gap_presync = 12
|
|
273
|
+
self.gapbyte = 0x4e
|
|
274
|
+
else:
|
|
275
|
+
raise error.Fatal('Unrecognised IBM mode')
|
|
276
|
+
self.img_bps: Optional[int] = None
|
|
277
|
+
|
|
278
|
+
@property
|
|
279
|
+
def nsec(self) -> int:
|
|
280
|
+
return len(self.sectors)
|
|
281
|
+
|
|
282
|
+
def summary_string(self) -> str:
|
|
283
|
+
nsec, nbad = len(self.sectors), self.nr_missing()
|
|
284
|
+
s = "%s (%d/%d sectors)" % (str(self.mode), nsec - nbad, nsec)
|
|
285
|
+
return s
|
|
286
|
+
|
|
287
|
+
def has_sec(self, sec_id: int):
|
|
288
|
+
return self.sectors[sec_id].crc == 0
|
|
289
|
+
|
|
290
|
+
def nr_missing(self) -> int:
|
|
291
|
+
return len(list(filter(lambda x: x.crc != 0, self.sectors)))
|
|
292
|
+
|
|
293
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
294
|
+
pos = 0
|
|
295
|
+
self.sectors.sort(key = lambda x: x.idam.r)
|
|
296
|
+
if self.img_bps is not None:
|
|
297
|
+
totsize = len(self.sectors) * self.img_bps
|
|
298
|
+
else:
|
|
299
|
+
totsize = functools.reduce(lambda x, y: x + len(y.dam.data),
|
|
300
|
+
self.sectors, 0)
|
|
301
|
+
if len(tdat) < totsize:
|
|
302
|
+
tdat += bytes(totsize - len(tdat))
|
|
303
|
+
for s in self.sectors:
|
|
304
|
+
s.crc = s.idam.crc = s.dam.crc = 0
|
|
305
|
+
size = len(s.dam.data)
|
|
306
|
+
s.dam.data = tdat[pos:pos+size]
|
|
307
|
+
if self.img_bps is not None:
|
|
308
|
+
pos += self.img_bps
|
|
309
|
+
else:
|
|
310
|
+
pos += size
|
|
311
|
+
self.sectors.sort(key = lambda x: x.start)
|
|
312
|
+
return totsize
|
|
313
|
+
|
|
314
|
+
def get_img_track(self) -> bytearray:
|
|
315
|
+
tdat = bytearray()
|
|
316
|
+
sectors = self.sectors.copy()
|
|
317
|
+
sectors.sort(key = lambda x: x.idam.r)
|
|
318
|
+
for s in sectors:
|
|
319
|
+
tdat += s.dam.data
|
|
320
|
+
if self.img_bps is not None:
|
|
321
|
+
tdat += bytes(self.img_bps - len(s.dam.data))
|
|
322
|
+
return tdat
|
|
323
|
+
|
|
324
|
+
def verify_track(self, flux) -> bool:
|
|
325
|
+
readback_track = self.__class__(self.cyl, self.head, self.mode)
|
|
326
|
+
readback_track.clock = self.clock
|
|
327
|
+
readback_track.time_per_rev = self.time_per_rev
|
|
328
|
+
for iam in self.iams:
|
|
329
|
+
readback_track.iams.append(copy.copy(iam))
|
|
330
|
+
for sec in self.sectors:
|
|
331
|
+
idam, dam = copy.copy(sec.idam), copy.copy(sec.dam)
|
|
332
|
+
idam.crc, dam.crc = 0xffff, 0xffff
|
|
333
|
+
readback_track.sectors.append(Sector(idam, dam))
|
|
334
|
+
readback_track.decode_flux(flux)
|
|
335
|
+
if readback_track.nr_missing() != 0:
|
|
336
|
+
return False
|
|
337
|
+
return self.sectors == readback_track.sectors
|
|
338
|
+
|
|
339
|
+
def mfm_master_track(self) -> bytes:
|
|
340
|
+
|
|
341
|
+
areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
|
|
342
|
+
t = bytes()
|
|
343
|
+
|
|
344
|
+
for a in areas:
|
|
345
|
+
start = a.start//16 - self.gap_presync
|
|
346
|
+
gap = max(start - len(t)//2, 0)
|
|
347
|
+
t += encode(bytes([self.gapbyte] * gap))
|
|
348
|
+
t += encode(bytes(self.gap_presync))
|
|
349
|
+
if isinstance(a, IAM):
|
|
350
|
+
t += mfm_iam_sync_bytes
|
|
351
|
+
t += encode(bytes([Mark.IAM]))
|
|
352
|
+
elif isinstance(a, Sector):
|
|
353
|
+
t += mfm_sync_bytes
|
|
354
|
+
idam = bytes([0xa1, 0xa1, 0xa1, Mark.IDAM,
|
|
355
|
+
a.idam.c, a.idam.h, a.idam.r, a.idam.n])
|
|
356
|
+
idam += struct.pack('>H', crc16.new(idam).crcValue)
|
|
357
|
+
t += encode(idam[3:])
|
|
358
|
+
start = a.dam.start//16 - self.gap_presync
|
|
359
|
+
gap = max(start - len(t)//2, 0)
|
|
360
|
+
t += encode(bytes([self.gapbyte] * gap))
|
|
361
|
+
t += encode(bytes(self.gap_presync))
|
|
362
|
+
t += mfm_sync_bytes
|
|
363
|
+
dam = bytes([0xa1, 0xa1, 0xa1, a.dam.mark]) + a.dam.data
|
|
364
|
+
dam += struct.pack('>H', crc16.new(dam).crcValue)
|
|
365
|
+
t += encode(dam[3:])
|
|
366
|
+
|
|
367
|
+
return t
|
|
368
|
+
|
|
369
|
+
def fm_master_track(self, mmfm_areas=None) -> bytes:
|
|
370
|
+
|
|
371
|
+
areas = heapq.merge(self.iams, self.sectors, key=lambda x:x.start)
|
|
372
|
+
t = bytes()
|
|
373
|
+
|
|
374
|
+
for a in areas:
|
|
375
|
+
start = a.start//16 - self.gap_presync
|
|
376
|
+
gap = max(start - len(t)//2, 0)
|
|
377
|
+
t += encode(bytes([self.gapbyte] * gap))
|
|
378
|
+
t += encode(bytes(self.gap_presync))
|
|
379
|
+
if isinstance(a, IAM):
|
|
380
|
+
t += fm_iam_sync_bytes
|
|
381
|
+
elif isinstance(a, Sector):
|
|
382
|
+
idam = bytes([Mark.IDAM,
|
|
383
|
+
a.idam.c, a.idam.h, a.idam.r, a.idam.n])
|
|
384
|
+
idam += struct.pack('>H', crc16.new(idam).crcValue)
|
|
385
|
+
t += sync(idam[0]) + encode(idam[1:])
|
|
386
|
+
start = a.dam.start//16 - self.gap_presync
|
|
387
|
+
gap = max(start - len(t)//2, 0)
|
|
388
|
+
t += encode(bytes([self.gapbyte] * gap))
|
|
389
|
+
t += encode(bytes(self.gap_presync))
|
|
390
|
+
dam = bytes([a.dam.mark]) + a.dam.data
|
|
391
|
+
dam += struct.pack('>H', crc16.new(dam).crcValue)
|
|
392
|
+
t += sync(dam[0])
|
|
393
|
+
if ((dam[0] & 0xfb) == Mark.DDAM_DEC_MMFM
|
|
394
|
+
and mmfm_areas is not None):
|
|
395
|
+
mmfm_areas.append((dec_mmfm.encode(dam[1:]), len(t)))
|
|
396
|
+
t += encode(bytes([self.gapbyte] * (128+2)))
|
|
397
|
+
else:
|
|
398
|
+
t += encode(dam[1:])
|
|
399
|
+
|
|
400
|
+
return t
|
|
401
|
+
|
|
402
|
+
def master_track(self) -> MasterTrack:
|
|
403
|
+
|
|
404
|
+
t: Union[bytes, bitarray]
|
|
405
|
+
|
|
406
|
+
if self.mode is Mode.FM:
|
|
407
|
+
t = self.fm_master_track()
|
|
408
|
+
elif self.mode is Mode.MFM:
|
|
409
|
+
t = self.mfm_master_track()
|
|
410
|
+
elif self.mode is Mode.DEC_RX02:
|
|
411
|
+
mmfm_areas: List[Tuple[bytes,int]] = list()
|
|
412
|
+
t = self.fm_master_track(mmfm_areas)
|
|
413
|
+
|
|
414
|
+
# Add the pre-index gap.
|
|
415
|
+
tlen = int((self.time_per_rev / self.clock) // 16)
|
|
416
|
+
gap = max(tlen - len(t)//2, 0)
|
|
417
|
+
t += encode(bytes([self.gapbyte] * gap))
|
|
418
|
+
|
|
419
|
+
if self.mode is Mode.FM:
|
|
420
|
+
t = fm_encode(t)
|
|
421
|
+
elif self.mode is Mode.MFM:
|
|
422
|
+
t = mfm_encode(t)
|
|
423
|
+
elif self.mode is Mode.DEC_RX02:
|
|
424
|
+
# Insert the MMFM areas into the doubled-rate FM track
|
|
425
|
+
b = bytearray(doubler(fm_encode(t)))
|
|
426
|
+
for a,o in mmfm_areas:
|
|
427
|
+
b[2*o:2*(o+256+2)] = a
|
|
428
|
+
# Insert the extra clock at the start of each MMFM area
|
|
429
|
+
bits = bitarray(endian='big')
|
|
430
|
+
bits.frombytes(b)
|
|
431
|
+
for _,o in reversed(mmfm_areas):
|
|
432
|
+
bits.insert(16*o, 0)
|
|
433
|
+
# We pass a bitarray to MasterTrack()
|
|
434
|
+
t = bits
|
|
435
|
+
|
|
436
|
+
track = MasterTrack(
|
|
437
|
+
bits = t,
|
|
438
|
+
time_per_rev = self.time_per_rev)
|
|
439
|
+
track.verify = self
|
|
440
|
+
return track
|
|
441
|
+
|
|
442
|
+
@staticmethod
|
|
443
|
+
def mfm_decode_raw(raw: PLLTrack) -> List[TrackArea]:
|
|
444
|
+
|
|
445
|
+
bits, _ = raw.get_all_data()
|
|
446
|
+
areas: List[TrackArea] = []
|
|
447
|
+
idam = None
|
|
448
|
+
|
|
449
|
+
## 1. Calculate offsets within dump
|
|
450
|
+
|
|
451
|
+
for offs in bits.search(mfm_iam_sync):
|
|
452
|
+
if len(bits) < offs+4*16:
|
|
453
|
+
continue
|
|
454
|
+
mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
|
|
455
|
+
if mark == Mark.IAM:
|
|
456
|
+
areas.append(IAM(offs, offs+4*16))
|
|
457
|
+
|
|
458
|
+
for offs in bits.search(mfm_sync):
|
|
459
|
+
|
|
460
|
+
if len(bits) < offs+4*16:
|
|
461
|
+
continue
|
|
462
|
+
mark = decode(bits[offs+3*16:offs+4*16].tobytes())[0]
|
|
463
|
+
if mark == Mark.IDAM:
|
|
464
|
+
s, e = offs, offs+10*16
|
|
465
|
+
if len(bits) < e:
|
|
466
|
+
continue
|
|
467
|
+
b = decode(bits[s:e].tobytes())
|
|
468
|
+
c,h,r,n = struct.unpack(">4x4B2x", b)
|
|
469
|
+
crc = crc16.new(b).crcValue
|
|
470
|
+
if idam is not None:
|
|
471
|
+
areas.append(idam)
|
|
472
|
+
idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
|
|
473
|
+
elif mark == Mark.DAM or mark == Mark.DDAM:
|
|
474
|
+
if idam is None or offs - idam.end > 1000:
|
|
475
|
+
areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
|
|
476
|
+
else:
|
|
477
|
+
sz = 128 << idam.n
|
|
478
|
+
s, e = offs, offs+(4+sz+2)*16
|
|
479
|
+
if len(bits) < e:
|
|
480
|
+
continue
|
|
481
|
+
b = decode(bits[s:e].tobytes())
|
|
482
|
+
crc = crc16.new(b).crcValue
|
|
483
|
+
dam = DAM(s, e, crc, mark=mark, data=b[4:-2])
|
|
484
|
+
areas.append(Sector(idam, dam))
|
|
485
|
+
idam = None
|
|
486
|
+
else:
|
|
487
|
+
pass # damaged tracks produce these constantly
|
|
488
|
+
|
|
489
|
+
if idam is not None:
|
|
490
|
+
areas.append(idam)
|
|
491
|
+
|
|
492
|
+
# Convert to offsets within track
|
|
493
|
+
areas.sort(key=lambda x:x.start)
|
|
494
|
+
index = iter([x.nr_bits for x in raw.revolutions])
|
|
495
|
+
p, n = 0, next(index)
|
|
496
|
+
for a in areas:
|
|
497
|
+
if a.start >= n:
|
|
498
|
+
p = n
|
|
499
|
+
try:
|
|
500
|
+
n += next(index)
|
|
501
|
+
except StopIteration:
|
|
502
|
+
n = float('inf')
|
|
503
|
+
a.delta(p)
|
|
504
|
+
areas.sort(key=lambda x:x.start)
|
|
505
|
+
|
|
506
|
+
return areas
|
|
507
|
+
|
|
508
|
+
@staticmethod
|
|
509
|
+
def fm_decode_raw(raw: PLLTrack,
|
|
510
|
+
mmfm_raw: Optional[PLLTrack] = None) -> List[TrackArea]:
|
|
511
|
+
|
|
512
|
+
bits, times = raw.get_all_data()
|
|
513
|
+
areas: List[TrackArea] = []
|
|
514
|
+
idam = None
|
|
515
|
+
|
|
516
|
+
if mmfm_raw is not None:
|
|
517
|
+
mmfm_bits, mmfm_times = mmfm_raw.get_all_data()
|
|
518
|
+
mmfm_iter = mmfm_bits.search(dec_mmfm.sync_prefix)
|
|
519
|
+
mmfm_offs = next(mmfm_iter, None)
|
|
520
|
+
fm_time, prev_fm_offs = 0.0, 0
|
|
521
|
+
mmfm_time, prev_mmfm_offs = 0.0, 0
|
|
522
|
+
|
|
523
|
+
## 1. Calculate offsets within dump
|
|
524
|
+
|
|
525
|
+
for offs in bits.search(fm_iam_sync):
|
|
526
|
+
offs += 16
|
|
527
|
+
areas.append(IAM(offs, offs+1*16))
|
|
528
|
+
|
|
529
|
+
for offs in bits.search(fm_sync_prefix):
|
|
530
|
+
|
|
531
|
+
# DEC MMFM track: Ensure this looks like an FM mark even at
|
|
532
|
+
# double rate. This also finds the equivalent point in the
|
|
533
|
+
# double-rate bitstream.
|
|
534
|
+
if mmfm_raw is not None:
|
|
535
|
+
fm_time += sum(times[prev_fm_offs:offs])
|
|
536
|
+
prev_fm_offs = offs
|
|
537
|
+
while mmfm_offs is not None:
|
|
538
|
+
mmfm_time += sum(mmfm_times[prev_mmfm_offs:mmfm_offs])
|
|
539
|
+
prev_mmfm_offs = mmfm_offs
|
|
540
|
+
delta = fm_time - mmfm_time
|
|
541
|
+
if delta < 1e-5:
|
|
542
|
+
break
|
|
543
|
+
mmfm_offs = next(mmfm_iter, None)
|
|
544
|
+
# We require a match within 10us in the double-rate bitstream.
|
|
545
|
+
if mmfm_offs is None or abs(delta) > 1e-5:
|
|
546
|
+
continue
|
|
547
|
+
|
|
548
|
+
offs += 16
|
|
549
|
+
if len(bits) < offs+1*16:
|
|
550
|
+
continue
|
|
551
|
+
mark = decode(bits[offs:offs+1*16].tobytes())[0]
|
|
552
|
+
clock = decode(bits[offs-1:offs+1*16-1].tobytes())[0]
|
|
553
|
+
if clock != 0xc7:
|
|
554
|
+
continue
|
|
555
|
+
if mark == Mark.IDAM:
|
|
556
|
+
s, e = offs, offs+7*16
|
|
557
|
+
if len(bits) < e:
|
|
558
|
+
continue
|
|
559
|
+
b = decode(bits[s:e].tobytes())
|
|
560
|
+
c,h,r,n = struct.unpack(">x4B2x", b)
|
|
561
|
+
crc = crc16.new(b).crcValue
|
|
562
|
+
if idam is not None:
|
|
563
|
+
areas.append(idam)
|
|
564
|
+
idam = IDAM(s, e, crc, c=c, h=h, r=r, n=n)
|
|
565
|
+
elif (mark == Mark.DAM or mark == Mark.DDAM
|
|
566
|
+
or mark == Mark.DAM_TRS80_DIR
|
|
567
|
+
or ((mark & 0xfb) == Mark.DDAM_DEC_MMFM
|
|
568
|
+
and mmfm_raw is not None)):
|
|
569
|
+
if idam is None or offs - idam.end > 1000:
|
|
570
|
+
areas.append(DAM(offs, offs+4*16, 0xffff, mark=mark))
|
|
571
|
+
continue
|
|
572
|
+
sz = 128 << idam.n
|
|
573
|
+
s, e = offs, offs+(1+sz+2)*16
|
|
574
|
+
if (mark & 0xfb) != Mark.DDAM_DEC_MMFM:
|
|
575
|
+
if len(bits) < e:
|
|
576
|
+
continue
|
|
577
|
+
b = decode(bits[s:e].tobytes())
|
|
578
|
+
else:
|
|
579
|
+
assert mmfm_offs is not None
|
|
580
|
+
ds, de = mmfm_offs+64+1, mmfm_offs+64+1+(sz*2+2)*16
|
|
581
|
+
if len(mmfm_bits) < de:
|
|
582
|
+
continue
|
|
583
|
+
b = bytes([mark]) + dec_mmfm.decode(mmfm_bits[ds:de])
|
|
584
|
+
crc = crc16.new(b).crcValue
|
|
585
|
+
dam = DAM(s, e, crc, mark=mark, data=b[1:-2])
|
|
586
|
+
areas.append(Sector(idam, dam))
|
|
587
|
+
idam = None
|
|
588
|
+
else:
|
|
589
|
+
pass # damaged tracks produce these constantly
|
|
590
|
+
|
|
591
|
+
if idam is not None:
|
|
592
|
+
areas.append(idam)
|
|
593
|
+
|
|
594
|
+
# Convert to offsets within track
|
|
595
|
+
areas.sort(key=lambda x:x.start)
|
|
596
|
+
index = iter([x.nr_bits for x in raw.revolutions])
|
|
597
|
+
p, n = 0, next(index)
|
|
598
|
+
for a in areas:
|
|
599
|
+
if a.start >= n:
|
|
600
|
+
p = n
|
|
601
|
+
try:
|
|
602
|
+
n += next(index)
|
|
603
|
+
except StopIteration:
|
|
604
|
+
n = float('inf')
|
|
605
|
+
a.delta(p)
|
|
606
|
+
areas.sort(key=lambda x:x.start)
|
|
607
|
+
|
|
608
|
+
return areas
|
|
609
|
+
|
|
610
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
611
|
+
flux = track.flux()
|
|
612
|
+
flux.cue_at_index()
|
|
613
|
+
raw = PLLTrack(time_per_rev = self.time_per_rev,
|
|
614
|
+
clock = self.clock, data = flux, pll = pll)
|
|
615
|
+
self.decode_raw(raw, pll, flux)
|
|
616
|
+
|
|
617
|
+
def decode_raw(self, raw: PLLTrack, pll: Optional[PLL],
|
|
618
|
+
flux: Flux) -> None:
|
|
619
|
+
|
|
620
|
+
if self.mode is Mode.FM:
|
|
621
|
+
areas = self.fm_decode_raw(raw)
|
|
622
|
+
elif self.mode is Mode.MFM:
|
|
623
|
+
areas = self.mfm_decode_raw(raw)
|
|
624
|
+
elif self.mode is Mode.DEC_RX02:
|
|
625
|
+
mmfm_raw = PLLTrack(time_per_rev = self.time_per_rev,
|
|
626
|
+
clock = self.clock/2, data = flux, pll = pll)
|
|
627
|
+
areas = self.fm_decode_raw(raw, mmfm_raw)
|
|
628
|
+
|
|
629
|
+
# Add to the deduped lists
|
|
630
|
+
for a in areas:
|
|
631
|
+
dupe = False
|
|
632
|
+
if isinstance(a, IAM):
|
|
633
|
+
for iam in self.iams:
|
|
634
|
+
if dupe := abs(iam.start - a.start) < 1000:
|
|
635
|
+
break
|
|
636
|
+
if not dupe:
|
|
637
|
+
self.iams.append(a)
|
|
638
|
+
elif isinstance(a, Sector):
|
|
639
|
+
for i, sec in enumerate(self.sectors):
|
|
640
|
+
if dupe := abs(sec.start - a.start) < 1000:
|
|
641
|
+
if sec.crc != 0 and a.crc == 0:
|
|
642
|
+
self.sectors[i] = a
|
|
643
|
+
break
|
|
644
|
+
if not dupe:
|
|
645
|
+
self.sectors.append(a)
|
|
646
|
+
self.iams.sort(key=lambda x:x.start)
|
|
647
|
+
self.sectors.sort(key=lambda x:x.start)
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
class IBMTrack_Fixed(IBMTrack):
|
|
651
|
+
|
|
652
|
+
def __init__(self, cyl: int, head: int, mode: Mode):
|
|
653
|
+
super().__init__(cyl, head, mode)
|
|
654
|
+
self.raw = IBMTrack(cyl, head, mode)
|
|
655
|
+
self.oversized = False
|
|
656
|
+
|
|
657
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
658
|
+
self.raw.clock = self.clock
|
|
659
|
+
self.raw.time_per_rev = self.time_per_rev
|
|
660
|
+
self.raw.decode_flux(track, pll)
|
|
661
|
+
mismatches = set()
|
|
662
|
+
for r in self.raw.sectors:
|
|
663
|
+
if r.idam.crc != 0:
|
|
664
|
+
continue
|
|
665
|
+
matched = False
|
|
666
|
+
for s in self.sectors:
|
|
667
|
+
if (s.idam.c == r.idam.c and
|
|
668
|
+
s.idam.h == r.idam.h and
|
|
669
|
+
s.idam.r == r.idam.r and
|
|
670
|
+
s.idam.n == r.idam.n):
|
|
671
|
+
s.idam.crc = 0
|
|
672
|
+
matched = True
|
|
673
|
+
if r.dam.crc == 0 and s.dam.crc != 0:
|
|
674
|
+
s.dam.crc = s.crc = 0
|
|
675
|
+
s.dam.data = r.dam.data
|
|
676
|
+
s.dam.mark = r.dam.mark
|
|
677
|
+
if not matched:
|
|
678
|
+
mismatches.add((r.idam.c, r.idam.h, r.idam.r, r.idam.n))
|
|
679
|
+
for m in mismatches:
|
|
680
|
+
print('T%d.%d: Ignoring unexpected sector C:%d H:%d R:%d N:%d'
|
|
681
|
+
% (self.cyl, self.head, *m))
|
|
682
|
+
|
|
683
|
+
@classmethod
|
|
684
|
+
def from_config(cls, config: IBMTrack_FixedDef, cyl: int, head: int,
|
|
685
|
+
warn_on_oversize = True):
|
|
686
|
+
|
|
687
|
+
def sec_n(i):
|
|
688
|
+
return config.sz[i] if i < len(config.sz) else config.sz[-1]
|
|
689
|
+
|
|
690
|
+
if config.format_name == 'dec.rx02':
|
|
691
|
+
mode, gaps, mark_dam = Mode.DEC_RX02, FMGaps, Mark.DAM_DEC_MMFM
|
|
692
|
+
synclen = 1 # Mark
|
|
693
|
+
elif config.format_name == 'ibm.mfm':
|
|
694
|
+
mode, gaps, mark_dam = Mode.MFM, MFMGaps, Mark.DAM
|
|
695
|
+
synclen = 4 # A1 A1 A1 Mark
|
|
696
|
+
elif config.format_name == 'ibm.fm':
|
|
697
|
+
mode, gaps, mark_dam = Mode.FM, FMGaps, Mark.DAM
|
|
698
|
+
synclen = 1 # Mark
|
|
699
|
+
|
|
700
|
+
t = cls(cyl, head, mode)
|
|
701
|
+
nsec = config.secs
|
|
702
|
+
t.img_bps = config.img_bps
|
|
703
|
+
|
|
704
|
+
if config.gapbyte is not None:
|
|
705
|
+
t.gapbyte = config.gapbyte
|
|
706
|
+
|
|
707
|
+
if config.iam:
|
|
708
|
+
gap1 = gaps.gap1 if config.gap1 is None else config.gap1
|
|
709
|
+
else:
|
|
710
|
+
gap1 = None
|
|
711
|
+
gap2 = gaps.gap2 if config.gap2 is None else config.gap2
|
|
712
|
+
gap3 = 0 if config.gap3 is None else config.gap3
|
|
713
|
+
gap4a = gaps.gap4a if config.gap4a is None else config.gap4a
|
|
714
|
+
|
|
715
|
+
idx_sz = gap4a
|
|
716
|
+
if gap1 is not None:
|
|
717
|
+
idx_sz += t.gap_presync + synclen + gap1
|
|
718
|
+
idam_sz = t.gap_presync + synclen + 4 + 2 + gap2
|
|
719
|
+
dam_sz_pre = t.gap_presync + synclen
|
|
720
|
+
dam_sz_post = 2 + gap3
|
|
721
|
+
|
|
722
|
+
tracklen = idx_sz + (idam_sz + dam_sz_pre + dam_sz_post) * nsec
|
|
723
|
+
for i in range(nsec):
|
|
724
|
+
tracklen += 128 << sec_n(i)
|
|
725
|
+
tracklen *= 16
|
|
726
|
+
|
|
727
|
+
rate, rpm = config.rate, config.rpm
|
|
728
|
+
if rate == 0:
|
|
729
|
+
# FM: 0 = Micro-diskette (125kbps), 1 = 8-inch disk (250kbps)
|
|
730
|
+
# MFM: 1 = DD (250kbps), 2 = HD (500kbps), 3 = ED (1000kbps)
|
|
731
|
+
for i in (range(2),range(1,4))[mode is Mode.MFM]:
|
|
732
|
+
maxlen = (50000*300//rpm) << i
|
|
733
|
+
maxlen += maxlen * 3 // 100 # Allow a few percent overage
|
|
734
|
+
if tracklen < maxlen:
|
|
735
|
+
break
|
|
736
|
+
rate = 125 << i
|
|
737
|
+
|
|
738
|
+
if mode is Mode.MFM and config.gap2 is None and rate >= 1000:
|
|
739
|
+
# At ED rate the default GAP2 is 41 bytes.
|
|
740
|
+
gap2 = 41
|
|
741
|
+
idam_sz += gap2 - gaps.gap2
|
|
742
|
+
tracklen += 16 * nsec * (gap2 - gaps.gap2)
|
|
743
|
+
|
|
744
|
+
tracklen_bc = rate * 400 * 300 // rpm
|
|
745
|
+
|
|
746
|
+
# Calculate a sensible gap3 value if none is manually specified
|
|
747
|
+
if nsec != 0 and config.gap3 is None:
|
|
748
|
+
space = max(0, tracklen_bc - tracklen)
|
|
749
|
+
no = sec_n(0)
|
|
750
|
+
gap3 = min(space // (16*nsec), gaps.gap3[no])
|
|
751
|
+
dam_sz_post += gap3
|
|
752
|
+
tracklen += 16 * nsec * gap3
|
|
753
|
+
|
|
754
|
+
# Allow for at least 1% pre-index gap, including final gap3
|
|
755
|
+
pre_index_sz = tracklen_bc // 100
|
|
756
|
+
if nsec != 0:
|
|
757
|
+
pre_index_sz = max(0, pre_index_sz - gap3 * 16)
|
|
758
|
+
tracklen += pre_index_sz
|
|
759
|
+
|
|
760
|
+
# Steal some post-index gap if there is insufficient pre-index gap
|
|
761
|
+
if tracklen > tracklen_bc and config.gap4a is None:
|
|
762
|
+
new_gap4a = gap4a // 2
|
|
763
|
+
idx_sz -= gap4a - new_gap4a
|
|
764
|
+
tracklen -= gap4a - new_gap4a
|
|
765
|
+
gap4a = new_gap4a
|
|
766
|
+
|
|
767
|
+
if tracklen > tracklen_bc * 105//100:
|
|
768
|
+
t.oversized = True
|
|
769
|
+
if warn_on_oversize:
|
|
770
|
+
print('T%d.%d: IBM: WARNING: Track is %.2f%% too long'
|
|
771
|
+
% (cyl, head, 100.0*tracklen/tracklen_bc))
|
|
772
|
+
tracklen_bc = max(tracklen_bc, tracklen)
|
|
773
|
+
|
|
774
|
+
t.time_per_rev = 60 / rpm
|
|
775
|
+
t.clock = t.time_per_rev / tracklen_bc
|
|
776
|
+
|
|
777
|
+
pos = gap4a
|
|
778
|
+
if gap1 is not None:
|
|
779
|
+
pos += t.gap_presync
|
|
780
|
+
t.iams = [IAM(pos*16,(pos+synclen)*16)]
|
|
781
|
+
pos += synclen + gap1
|
|
782
|
+
|
|
783
|
+
id0 = config.id
|
|
784
|
+
h = head if config.h is None else config.h
|
|
785
|
+
for sec in sec_map(nsec, config.interleave,
|
|
786
|
+
config.cskew, config.hskew, cyl, head):
|
|
787
|
+
pos += t.gap_presync
|
|
788
|
+
idam = IDAM(pos*16, (pos+synclen+4+2)*16, 0xffff,
|
|
789
|
+
c = cyl, h = h, r = id0+sec, n = sec_n(sec))
|
|
790
|
+
pos += synclen + 4 + 2 + gap2 + t.gap_presync
|
|
791
|
+
size = 128 << idam.n
|
|
792
|
+
datsz = size*2 if mark_dam == Mark.DAM_DEC_MMFM else size
|
|
793
|
+
dam = DAM(pos*16, (pos+synclen+size+2)*16, 0xffff,
|
|
794
|
+
mark=mark_dam, data=b'-=[BAD SECTOR]=-'*(datsz//16))
|
|
795
|
+
t.sectors.append(Sector(idam, dam))
|
|
796
|
+
pos += synclen + size + 2 + gap3
|
|
797
|
+
|
|
798
|
+
return t
|
|
799
|
+
|
|
800
|
+
|
|
801
|
+
class IBMTrack_FixedDef(codec.TrackDef):
|
|
802
|
+
|
|
803
|
+
default_revs = default_revs
|
|
804
|
+
|
|
805
|
+
def __init__(self, format_name: str):
|
|
806
|
+
self.secs = 0
|
|
807
|
+
self.sz: List[int] = []
|
|
808
|
+
self.id = 1
|
|
809
|
+
self.h: Optional[int] = None
|
|
810
|
+
self.format_name = format_name
|
|
811
|
+
self.interleave = 1
|
|
812
|
+
self.cskew, self.hskew = 0, 0
|
|
813
|
+
self.rpm = 300
|
|
814
|
+
self.gap1: Optional[int] = None
|
|
815
|
+
self.gap2: Optional[int] = None
|
|
816
|
+
self.gap3: Optional[int] = None
|
|
817
|
+
self.gap4a: Optional[int] = None
|
|
818
|
+
self.gapbyte: Optional[int] = None
|
|
819
|
+
self.iam = True
|
|
820
|
+
self.rate = 0
|
|
821
|
+
self.img_bps: Optional[int] = None
|
|
822
|
+
self.finalised = False
|
|
823
|
+
|
|
824
|
+
def add_param(self, key: str, val: str) -> None:
|
|
825
|
+
if key == 'secs':
|
|
826
|
+
n = int(val)
|
|
827
|
+
error.check(0 <= n <= 256, '%s out of range' % key)
|
|
828
|
+
self.secs = n
|
|
829
|
+
elif key == 'bps':
|
|
830
|
+
self.sz = []
|
|
831
|
+
for x in val.split(','):
|
|
832
|
+
y = re.match(r'(\d+)\*(\d+)', x)
|
|
833
|
+
if y is not None:
|
|
834
|
+
n, l = int(y.group(1)), int(y.group(2))
|
|
835
|
+
else:
|
|
836
|
+
n, l = int(x), 1
|
|
837
|
+
s = 0
|
|
838
|
+
while n != 128<<s:
|
|
839
|
+
s += 1
|
|
840
|
+
error.check(s <= 6, 'bps value out of range')
|
|
841
|
+
for _ in range(l):
|
|
842
|
+
self.sz.append(s)
|
|
843
|
+
elif key == 'interleave':
|
|
844
|
+
n = int(val)
|
|
845
|
+
error.check(1 <= n <= 255, '%s out of range' % key)
|
|
846
|
+
self.interleave = n
|
|
847
|
+
elif key in ['id', 'cskew', 'hskew']:
|
|
848
|
+
n = int(val, base=0)
|
|
849
|
+
error.check(0 <= n <= 255, '%s out of range' % key)
|
|
850
|
+
setattr(self, key, n)
|
|
851
|
+
elif key in ['gap1', 'gap2', 'gap3', 'gap4a', 'gapbyte', 'h']:
|
|
852
|
+
if val == 'auto':
|
|
853
|
+
n = None
|
|
854
|
+
else:
|
|
855
|
+
n = int(val, base=0)
|
|
856
|
+
error.check(0 <= n <= 255, '%s out of range' % key)
|
|
857
|
+
setattr(self, key, n)
|
|
858
|
+
elif key == 'iam':
|
|
859
|
+
error.check(val in ['yes', 'no'], 'bad iam value')
|
|
860
|
+
self.iam = val == 'yes'
|
|
861
|
+
elif key in ['rate', 'rpm']:
|
|
862
|
+
n = int(val)
|
|
863
|
+
error.check(1 <= n <= 2000, '%s out of range' % key)
|
|
864
|
+
setattr(self, key, n)
|
|
865
|
+
elif key == 'img_bps':
|
|
866
|
+
n = int(val)
|
|
867
|
+
error.check(128 <= n <= 8192, '%s out of range' % key)
|
|
868
|
+
self.img_bps = n
|
|
869
|
+
else:
|
|
870
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
871
|
+
|
|
872
|
+
def finalise(self) -> None:
|
|
873
|
+
if self.finalised:
|
|
874
|
+
return
|
|
875
|
+
error.check(self.iam or self.gap1 is None,
|
|
876
|
+
'gap1 specified but no iam')
|
|
877
|
+
error.check(self.secs == 0 or len(self.sz) != 0,
|
|
878
|
+
'sector size not specified')
|
|
879
|
+
error.check((self.img_bps is None
|
|
880
|
+
or self.img_bps >= max(self.sz, default=0)),
|
|
881
|
+
'img_bps cannot be smaller than sector data size')
|
|
882
|
+
self.finalised = True
|
|
883
|
+
|
|
884
|
+
def mk_track(self, cyl: int, head: int) -> IBMTrack_Fixed:
|
|
885
|
+
return IBMTrack_Fixed.from_config(self, cyl, head)
|
|
886
|
+
|
|
887
|
+
|
|
888
|
+
class IBMTrack_Empty(IBMTrack):
|
|
889
|
+
|
|
890
|
+
def __init__(self, cyl, head):
|
|
891
|
+
# Fake some parameters for master_track()
|
|
892
|
+
super().__init__(cyl, head, Mode.MFM)
|
|
893
|
+
self.time_per_rev = 0.2
|
|
894
|
+
self.clock = 2e-6
|
|
895
|
+
|
|
896
|
+
def summary_string(self) -> str:
|
|
897
|
+
return "IBM Empty"
|
|
898
|
+
|
|
899
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
900
|
+
raise error.Fatal('ibm.scan: Cannot handle IMG input data')
|
|
901
|
+
|
|
902
|
+
|
|
903
|
+
class IBMTrack_Scan(codec.Codec):
|
|
904
|
+
|
|
905
|
+
RATES = [ 125, 250, 500 ]
|
|
906
|
+
RPMS = [ 300, 360 ]
|
|
907
|
+
BEST_GUESS = None
|
|
908
|
+
|
|
909
|
+
def __init__(self, cyl: int, head: int, config: IBMTrack_ScanDef):
|
|
910
|
+
self.cyl, self.head = cyl, head
|
|
911
|
+
self.rate, self.rpm = config.rate, config.rpm
|
|
912
|
+
self.track: IBMTrack = IBMTrack_Empty(cyl, head)
|
|
913
|
+
|
|
914
|
+
@property
|
|
915
|
+
def nsec(self) -> int:
|
|
916
|
+
return self.track.nsec
|
|
917
|
+
|
|
918
|
+
def summary_string(self) -> str:
|
|
919
|
+
return self.track.summary_string()
|
|
920
|
+
|
|
921
|
+
def has_sec(self, sec_id: int):
|
|
922
|
+
return self.track.has_sec(sec_id)
|
|
923
|
+
|
|
924
|
+
def nr_missing(self) -> int:
|
|
925
|
+
return self.track.nr_missing()
|
|
926
|
+
|
|
927
|
+
def master_track(self) -> MasterTrack:
|
|
928
|
+
return self.track.master_track()
|
|
929
|
+
|
|
930
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
931
|
+
return self.track.set_img_track(tdat)
|
|
932
|
+
|
|
933
|
+
def get_img_track(self) -> bytearray:
|
|
934
|
+
return self.track.get_img_track()
|
|
935
|
+
|
|
936
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
937
|
+
|
|
938
|
+
# Add more data to an existing track instance?
|
|
939
|
+
if not isinstance(self.track, IBMTrack_Empty):
|
|
940
|
+
self.track.decode_flux(track, pll)
|
|
941
|
+
return
|
|
942
|
+
|
|
943
|
+
# Try our best guess first.
|
|
944
|
+
if IBMTrack_Scan.BEST_GUESS is not None:
|
|
945
|
+
time_per_rev, clock, mode = IBMTrack_Scan.BEST_GUESS
|
|
946
|
+
t = IBMTrack(self.cyl, self.head, mode)
|
|
947
|
+
t.clock, t.time_per_rev = clock, time_per_rev
|
|
948
|
+
t.decode_flux(track, pll)
|
|
949
|
+
# Perfect match, no missing sectors?
|
|
950
|
+
if t.nsec != 0 and t.nr_missing() == 0:
|
|
951
|
+
self.track = t
|
|
952
|
+
return
|
|
953
|
+
|
|
954
|
+
rates = self.RATES if self.rate is None else [self.rate]
|
|
955
|
+
rpms = self.RPMS if self.rpm is None else [self.rpm]
|
|
956
|
+
|
|
957
|
+
flux = track.flux()
|
|
958
|
+
flux.cue_at_index()
|
|
959
|
+
|
|
960
|
+
# Scan at various rates & modes to find at least one sector
|
|
961
|
+
for rpm in rpms:
|
|
962
|
+
time_per_rev = 60 / rpm
|
|
963
|
+
for rate in rates:
|
|
964
|
+
clock = 5e-4 / rate
|
|
965
|
+
raw = PLLTrack(time_per_rev = time_per_rev,
|
|
966
|
+
clock = clock, data = flux, pll = pll)
|
|
967
|
+
for mode in [Mode.MFM, Mode.FM]:
|
|
968
|
+
t = IBMTrack(self.cyl, self.head, mode)
|
|
969
|
+
t.clock, t.time_per_rev = clock, time_per_rev
|
|
970
|
+
t.decode_raw(raw, pll, flux)
|
|
971
|
+
if ((t.nsec - t.nr_missing())
|
|
972
|
+
> (self.track.nsec - self.track.nr_missing())):
|
|
973
|
+
self.track = t
|
|
974
|
+
|
|
975
|
+
# If we found a match, remember it as a best guess for the next track.
|
|
976
|
+
if not isinstance(self.track, IBMTrack_Empty):
|
|
977
|
+
t = self.track
|
|
978
|
+
IBMTrack_Scan.BEST_GUESS = (t.time_per_rev, t.clock, t.mode)
|
|
979
|
+
|
|
980
|
+
|
|
981
|
+
class IBMTrack_ScanDef(codec.TrackDef):
|
|
982
|
+
|
|
983
|
+
default_revs = default_revs
|
|
984
|
+
|
|
985
|
+
def __init__(self, format_name: str):
|
|
986
|
+
self.rpm: Optional[int] = None
|
|
987
|
+
self.rate: Optional[int] = None
|
|
988
|
+
|
|
989
|
+
def add_param(self, key: str, val: str) -> None:
|
|
990
|
+
if key in ['rate', 'rpm']:
|
|
991
|
+
n = int(val)
|
|
992
|
+
error.check(1 <= n <= 2000, '%s out of range' % key)
|
|
993
|
+
setattr(self, key, n)
|
|
994
|
+
else:
|
|
995
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
996
|
+
|
|
997
|
+
def finalise(self) -> None:
|
|
998
|
+
pass
|
|
999
|
+
|
|
1000
|
+
def mk_track(self, cyl: int, head: int) -> IBMTrack_Scan:
|
|
1001
|
+
return IBMTrack_Scan(cyl, head, self)
|
|
1002
|
+
|
|
1003
|
+
# Local variables:
|
|
1004
|
+
# python-indent: 4
|
|
1005
|
+
# End:
|