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,742 @@
|
|
|
1
|
+
# greaseweazle/image/hfe.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 cast, Dict, Tuple, Optional, List
|
|
10
|
+
|
|
11
|
+
import struct
|
|
12
|
+
import itertools as it
|
|
13
|
+
|
|
14
|
+
from diskstack._vendor.greaseweazle import error
|
|
15
|
+
from diskstack._vendor.greaseweazle.tools import util
|
|
16
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
17
|
+
from diskstack._vendor.greaseweazle.codec.ibm import ibm
|
|
18
|
+
from diskstack._vendor.greaseweazle.codec.apple2 import apple2_gcr
|
|
19
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLLTrack
|
|
20
|
+
from bitarray import bitarray
|
|
21
|
+
from .image import Image, ImageOpts
|
|
22
|
+
|
|
23
|
+
InterfaceMode = {
|
|
24
|
+
'IBMPC_DD': 0x00,
|
|
25
|
+
'IBMPC_HD': 0x01,
|
|
26
|
+
'ATARIST_DD': 0x02,
|
|
27
|
+
'ATARIST_HD': 0x03,
|
|
28
|
+
'AMIGA_DD': 0x04,
|
|
29
|
+
'AMIGA_HD': 0x05,
|
|
30
|
+
'CPC_DD': 0x06,
|
|
31
|
+
'GENERIC_SHUGART_DD': 0x07,
|
|
32
|
+
'IBMPC_ED': 0x08,
|
|
33
|
+
'MSX2_DD': 0x09,
|
|
34
|
+
'C64_DD': 0x0A,
|
|
35
|
+
'EMU_SHUGART': 0x0B,
|
|
36
|
+
'S950_DD': 0x0C,
|
|
37
|
+
'S950_HD': 0x0D,
|
|
38
|
+
'S950_DD_HD': 0x0E,
|
|
39
|
+
'IBMPC_DD_HD': 0x0F,
|
|
40
|
+
'QUICKDISK': 0x10,
|
|
41
|
+
'UNKNOWN': 0xFF
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
EncodingType = {
|
|
45
|
+
'ISOIBM_MFM': 0x00,
|
|
46
|
+
'AMIGA_MFM': 0x01,
|
|
47
|
+
'ISOIBM_FM': 0x02,
|
|
48
|
+
'EMU_FM': 0x03,
|
|
49
|
+
'TYCOM_FM': 0x04,
|
|
50
|
+
'MEMBRAIN_MFM': 0x05,
|
|
51
|
+
'APPLEII_GCR1': 0x06,
|
|
52
|
+
'APPLEII_GCR2': 0x07,
|
|
53
|
+
'APPLEII_HDDD_A2_GCR1': 0x08,
|
|
54
|
+
'APPLEII_HDDD_A2_GCR2': 0x09,
|
|
55
|
+
'ARBURGDAT': 0x0A,
|
|
56
|
+
'ARBURGSYS': 0x0B,
|
|
57
|
+
'AED6200P_MFM': 0x0C,
|
|
58
|
+
'NORTHSTAR_HS_MFM': 0x0D,
|
|
59
|
+
'HEATHKIT_HS_FM': 0x0E,
|
|
60
|
+
'DEC_RX02_M2FM': 0x0F,
|
|
61
|
+
'APPLEMAC_GCR': 0x10,
|
|
62
|
+
'QD_MO5': 0x11,
|
|
63
|
+
'C64_GCR': 0x12,
|
|
64
|
+
'VICTOR9K_GCR': 0x13,
|
|
65
|
+
'MICRALN_HS_FM': 0x14,
|
|
66
|
+
'UNKNOWN': 0xFF
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
class HFEOpts(ImageOpts):
|
|
70
|
+
"""bitrate: Bitrate of new HFE image file.
|
|
71
|
+
"""
|
|
72
|
+
|
|
73
|
+
w_settings = [ 'bitrate', 'version', 'interface', 'encoding',
|
|
74
|
+
'double_step', 'uniform' ]
|
|
75
|
+
|
|
76
|
+
def __init__(self) -> None:
|
|
77
|
+
self._bitrate: Optional[int] = None
|
|
78
|
+
self._version = 1
|
|
79
|
+
self._interface = 0xff
|
|
80
|
+
self._encoding = 0xff
|
|
81
|
+
self.double_step = False
|
|
82
|
+
self.uniform = False
|
|
83
|
+
|
|
84
|
+
@property
|
|
85
|
+
def bitrate(self) -> Optional[int]:
|
|
86
|
+
return self._bitrate
|
|
87
|
+
@bitrate.setter
|
|
88
|
+
def bitrate(self, bitrate: float):
|
|
89
|
+
try:
|
|
90
|
+
self._bitrate = int(bitrate)
|
|
91
|
+
if self._bitrate <= 0:
|
|
92
|
+
raise ValueError
|
|
93
|
+
except ValueError:
|
|
94
|
+
raise error.Fatal("HFE: Invalid bitrate: '%s'" % bitrate)
|
|
95
|
+
|
|
96
|
+
@property
|
|
97
|
+
def version(self) -> int:
|
|
98
|
+
return self._version
|
|
99
|
+
@version.setter
|
|
100
|
+
def version(self, version) -> None:
|
|
101
|
+
try:
|
|
102
|
+
self._version = int(version)
|
|
103
|
+
if self._version != 1 and self._version != 3:
|
|
104
|
+
raise ValueError
|
|
105
|
+
except ValueError:
|
|
106
|
+
raise error.Fatal("HFE: Invalid version: '%s'" % version)
|
|
107
|
+
|
|
108
|
+
@property
|
|
109
|
+
def interface(self):
|
|
110
|
+
return self._interface
|
|
111
|
+
@interface.setter
|
|
112
|
+
def interface(self, mode):
|
|
113
|
+
try:
|
|
114
|
+
self._interface = InterfaceMode[mode.upper()]
|
|
115
|
+
except KeyError:
|
|
116
|
+
try:
|
|
117
|
+
self._interface = int(mode, 0)
|
|
118
|
+
except ValueError:
|
|
119
|
+
l = [ x.lower() for x in InterfaceMode.keys() ]
|
|
120
|
+
l.sort()
|
|
121
|
+
raise error.Fatal("Bad HFE interface mode: '%s'\n" % mode
|
|
122
|
+
+ 'Valid modes:\n' + util.columnify(l))
|
|
123
|
+
|
|
124
|
+
@property
|
|
125
|
+
def encoding(self):
|
|
126
|
+
return self._encoding
|
|
127
|
+
@encoding.setter
|
|
128
|
+
def encoding(self, enc):
|
|
129
|
+
try:
|
|
130
|
+
self._encoding = EncodingType[enc.upper()]
|
|
131
|
+
except KeyError:
|
|
132
|
+
try:
|
|
133
|
+
self._encoding = int(enc, 0)
|
|
134
|
+
except ValueError:
|
|
135
|
+
l = [ x.lower() for x in EncodingType.keys() ]
|
|
136
|
+
l.sort()
|
|
137
|
+
raise error.Fatal("Bad HFE encoding type: '%s'\n" % enc
|
|
138
|
+
+ 'Valid types:\n' + util.columnify(l))
|
|
139
|
+
|
|
140
|
+
class HFETrack:
|
|
141
|
+
def __init__(self, track: MasterTrack) -> None:
|
|
142
|
+
self.track = track
|
|
143
|
+
|
|
144
|
+
@classmethod
|
|
145
|
+
def from_bitarray(cls, bits: bitarray, bitrate: int) -> HFETrack:
|
|
146
|
+
return cls(MasterTrack(
|
|
147
|
+
bits = bits, time_per_rev = len(bits) / (2000*bitrate)))
|
|
148
|
+
|
|
149
|
+
@classmethod
|
|
150
|
+
def from_hfe_bytes(cls, b: bytes, bitrate: int) -> HFETrack:
|
|
151
|
+
bits = bitarray(endian='big')
|
|
152
|
+
bits.frombytes(b)
|
|
153
|
+
bits.bytereverse()
|
|
154
|
+
return cls.from_bitarray(bits, bitrate)
|
|
155
|
+
|
|
156
|
+
def to_hfe_bytes(self) -> bytes:
|
|
157
|
+
bits = bitarray(endian='big')
|
|
158
|
+
bits.frombytes(self.track.bits.tobytes())
|
|
159
|
+
bits.bytereverse()
|
|
160
|
+
return bits.tobytes()
|
|
161
|
+
|
|
162
|
+
|
|
163
|
+
class HFE(Image):
|
|
164
|
+
|
|
165
|
+
opts: HFEOpts
|
|
166
|
+
|
|
167
|
+
def __init__(self, name: str, _fmt) -> None:
|
|
168
|
+
self.opts = HFEOpts()
|
|
169
|
+
self.filename = name
|
|
170
|
+
# Each track is (bitlen, rawbytes).
|
|
171
|
+
# rawbytes is a bytes() object in little-endian bit order.
|
|
172
|
+
self.to_track: Dict[Tuple[int,int], HFETrack] = dict()
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def from_bytes(self, dat: bytes) -> None:
|
|
176
|
+
|
|
177
|
+
(sig, f_rev, n_cyl, n_side, t_enc, bitrate,
|
|
178
|
+
_, _, _, tlut_base) = struct.unpack("<8s4B2H2BH", dat[:20])
|
|
179
|
+
if sig == b"HXCHFEV3":
|
|
180
|
+
version = 3
|
|
181
|
+
else:
|
|
182
|
+
error.check(sig == b"HXCPICFE", "Not a valid HFE file")
|
|
183
|
+
version = 1
|
|
184
|
+
error.check(f_rev <= 1, "Not a valid HFE file")
|
|
185
|
+
error.check(0 < n_cyl, "HFE: Invalid #cyls")
|
|
186
|
+
error.check(0 < n_side < 3, "HFE: Invalid #sides")
|
|
187
|
+
|
|
188
|
+
self.opts.bitrate = bitrate
|
|
189
|
+
self.opts.version = version
|
|
190
|
+
|
|
191
|
+
tlut = dat[tlut_base*512:tlut_base*512+n_cyl*4]
|
|
192
|
+
|
|
193
|
+
for cyl in range(n_cyl):
|
|
194
|
+
for side in range(n_side):
|
|
195
|
+
offset, length = struct.unpack("<2H", tlut[cyl*4:(cyl+1)*4])
|
|
196
|
+
todo = length // 2
|
|
197
|
+
tdat = bytes()
|
|
198
|
+
while todo:
|
|
199
|
+
d_off = offset*512 + side*256
|
|
200
|
+
d_nr = 256 if todo > 256 else todo
|
|
201
|
+
tdat += dat[d_off:d_off+d_nr]
|
|
202
|
+
todo -= d_nr
|
|
203
|
+
offset += 1
|
|
204
|
+
track_v1 = HFETrack.from_hfe_bytes(tdat, bitrate)
|
|
205
|
+
if version == 1:
|
|
206
|
+
self.to_track[cyl,side] = track_v1
|
|
207
|
+
else:
|
|
208
|
+
self.to_track[cyl,side] = hfev3_mk_track(
|
|
209
|
+
cyl, side, track_v1)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def get_track(self, cyl: int, side: int) -> Optional[MasterTrack]:
|
|
213
|
+
if (cyl,side) not in self.to_track:
|
|
214
|
+
return None
|
|
215
|
+
return self.to_track[cyl,side].track
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def emit_track(self, cyl: int, side: int, track) -> None:
|
|
219
|
+
# HFE convention is that FM and GCR are recorded at double rate
|
|
220
|
+
double_rate = (
|
|
221
|
+
(isinstance(track, ibm.IBMTrack) and track.mode is ibm.Mode.FM)
|
|
222
|
+
or isinstance(track, apple2_gcr.Apple2GCR))
|
|
223
|
+
t = track.master_track() if isinstance(track, codec.Codec) else track
|
|
224
|
+
if self.opts.bitrate is None:
|
|
225
|
+
error.check(hasattr(t, 'bitrate'),
|
|
226
|
+
'HFE: Requires bitrate to be specified'
|
|
227
|
+
' (eg. filename.hfe::bitrate=500)')
|
|
228
|
+
self.opts.bitrate = round(t.bitrate / 2e3)
|
|
229
|
+
assert self.opts.bitrate is not None # mypy
|
|
230
|
+
if double_rate:
|
|
231
|
+
self.opts.bitrate *= 2
|
|
232
|
+
print('HFE: Data bitrate detected: %d kbit/s' % self.opts.bitrate)
|
|
233
|
+
if isinstance(t, MasterTrack):
|
|
234
|
+
# Rotate data and timings to start at the index.
|
|
235
|
+
index = -t.splice % len(t.bits)
|
|
236
|
+
bits = t.bits[index:] + t.bits[:index]
|
|
237
|
+
bit_ticks = (t.bit_ticks[index:] + t.bit_ticks[:index]
|
|
238
|
+
if t.bit_ticks else None)
|
|
239
|
+
hardsector_bits = t.hardsector_bits
|
|
240
|
+
# Rotate the weak areas
|
|
241
|
+
weak = []
|
|
242
|
+
for s, n in t.weak:
|
|
243
|
+
s -= t.splice
|
|
244
|
+
if s < 0:
|
|
245
|
+
if s + n > 0:
|
|
246
|
+
weak.append((s % len(t.bits), -s))
|
|
247
|
+
weak.append((0, s + n))
|
|
248
|
+
else:
|
|
249
|
+
weak.append((s % len(t.bits), n))
|
|
250
|
+
else:
|
|
251
|
+
weak.append((s, n))
|
|
252
|
+
if double_rate:
|
|
253
|
+
double_bytes = ibm.doubler(bits.tobytes())
|
|
254
|
+
double_bits = bitarray(endian='big')
|
|
255
|
+
double_bits.frombytes(double_bytes)
|
|
256
|
+
bits = double_bits[:2*len(bits)]
|
|
257
|
+
if bit_ticks is not None:
|
|
258
|
+
bit_ticks = [x for x in bit_ticks for _ in range(2)]
|
|
259
|
+
weak = [(2*x,2*y) for (x,y) in weak]
|
|
260
|
+
if hardsector_bits is not None:
|
|
261
|
+
hardsector_bits = [2*x for x in hardsector_bits]
|
|
262
|
+
mt = MasterTrack(
|
|
263
|
+
bits = bits, time_per_rev = t.time_per_rev,
|
|
264
|
+
bit_ticks = bit_ticks, weak = weak,
|
|
265
|
+
hardsector_bits = hardsector_bits)
|
|
266
|
+
else:
|
|
267
|
+
flux = t.flux()
|
|
268
|
+
flux.cue_at_index()
|
|
269
|
+
raw = PLLTrack(clock = 5e-4 / self.opts.bitrate, data = flux)
|
|
270
|
+
bits, bit_ticks = raw.get_revolution(0)
|
|
271
|
+
if self.opts.uniform:
|
|
272
|
+
bit_ticks = None
|
|
273
|
+
mt = MasterTrack(
|
|
274
|
+
bits = bits, time_per_rev = flux.time_per_rev,
|
|
275
|
+
bit_ticks = bit_ticks,
|
|
276
|
+
hardsector_bits = raw.revolutions[0].hardsector_bits)
|
|
277
|
+
self.to_track[cyl,side] = HFETrack(mt)
|
|
278
|
+
|
|
279
|
+
|
|
280
|
+
def hfev1_get_image(self) -> bytes:
|
|
281
|
+
|
|
282
|
+
assert self.opts.version == 1
|
|
283
|
+
assert self.opts.bitrate is not None
|
|
284
|
+
|
|
285
|
+
n_side = 1
|
|
286
|
+
n_cyl = max(self.to_track.keys(), default=(0,), key=lambda x:x[0])[0]
|
|
287
|
+
n_cyl += 1
|
|
288
|
+
|
|
289
|
+
# We dynamically build the Track-LUT and -Data arrays.
|
|
290
|
+
tlut = bytearray()
|
|
291
|
+
tdat = bytearray()
|
|
292
|
+
|
|
293
|
+
# Stuff real data into the image.
|
|
294
|
+
for i in range(n_cyl):
|
|
295
|
+
s0 = self.to_track[i,0] if (i,0) in self.to_track else None
|
|
296
|
+
s1 = self.to_track[i,1] if (i,1) in self.to_track else None
|
|
297
|
+
if s0 is None and s1 is None:
|
|
298
|
+
# Dummy data for empty cylinders. Assumes 300RPM.
|
|
299
|
+
nr_bytes = 100 * self.opts.bitrate
|
|
300
|
+
tlut += struct.pack("<2H", len(tdat)//512 + 2, nr_bytes)
|
|
301
|
+
tdat += bytes([0x88] * (nr_bytes+0x1ff & ~0x1ff))
|
|
302
|
+
else:
|
|
303
|
+
# At least one side of this cylinder is populated.
|
|
304
|
+
if s1 is not None:
|
|
305
|
+
n_side = 2
|
|
306
|
+
bc = [s0.to_hfe_bytes() if s0 is not None else bytes(),
|
|
307
|
+
s1.to_hfe_bytes() if s1 is not None else bytes()]
|
|
308
|
+
nr_bytes = max(len(t) for t in bc)
|
|
309
|
+
nr_blocks = (nr_bytes + 0xff) // 0x100
|
|
310
|
+
tlut += struct.pack("<2H", len(tdat)//512 + 2, 2 * nr_bytes)
|
|
311
|
+
for b in range(nr_blocks):
|
|
312
|
+
for t in bc:
|
|
313
|
+
slice = t[b*256:(b+1)*256]
|
|
314
|
+
tdat += slice + bytes([0x88] * (256 - len(slice)))
|
|
315
|
+
|
|
316
|
+
# Construct the image header.
|
|
317
|
+
header = struct.pack("<8s4B2H2BH2B",
|
|
318
|
+
b"HXCPICFE",
|
|
319
|
+
0,
|
|
320
|
+
n_cyl,
|
|
321
|
+
n_side,
|
|
322
|
+
self.opts.encoding,
|
|
323
|
+
self.opts.bitrate,
|
|
324
|
+
0, # rpm (unused)
|
|
325
|
+
self.opts.interface,
|
|
326
|
+
1, # rsvd
|
|
327
|
+
1, # track list offset
|
|
328
|
+
0xff, # write_allowed
|
|
329
|
+
0xff if not self.opts.double_step else 0)
|
|
330
|
+
|
|
331
|
+
# Pad the header and TLUT to 512-byte blocks.
|
|
332
|
+
header += bytes([0xff] * (0x200 - len(header)))
|
|
333
|
+
tlut += bytes([0xff] * (0x200 - len(tlut)))
|
|
334
|
+
|
|
335
|
+
return header + tlut + tdat
|
|
336
|
+
|
|
337
|
+
|
|
338
|
+
def get_image(self) -> bytes:
|
|
339
|
+
|
|
340
|
+
# Empty disk may have no bitrate
|
|
341
|
+
if self.opts.bitrate is None:
|
|
342
|
+
assert not self.to_track
|
|
343
|
+
self.opts.bitrate = 250
|
|
344
|
+
|
|
345
|
+
if self.opts.version == 3:
|
|
346
|
+
return hfev3_get_image(self)
|
|
347
|
+
|
|
348
|
+
try:
|
|
349
|
+
return self.hfev1_get_image()
|
|
350
|
+
except struct.error as e:
|
|
351
|
+
raise error.Fatal(
|
|
352
|
+
'''\
|
|
353
|
+
HFE: Track too long to fit in image!
|
|
354
|
+
Are you trying to create an ED-rate image?
|
|
355
|
+
If so: You can't. Use another image format.''')
|
|
356
|
+
|
|
357
|
+
|
|
358
|
+
###
|
|
359
|
+
### HFE V3 TRACK PARSER
|
|
360
|
+
###
|
|
361
|
+
|
|
362
|
+
class HFEv3_Op:
|
|
363
|
+
Nop = 0xf0
|
|
364
|
+
Index = 0xf1
|
|
365
|
+
Bitrate = 0xf2
|
|
366
|
+
SkipBits = 0xf3
|
|
367
|
+
Rand = 0xf4
|
|
368
|
+
|
|
369
|
+
class HFEv3_Range:
|
|
370
|
+
def __init__(self, s: int, n: int, val=0):
|
|
371
|
+
self.s, self.n, self.val = s, n, val
|
|
372
|
+
@property
|
|
373
|
+
def e(self) -> int:
|
|
374
|
+
return self.s + self.n
|
|
375
|
+
|
|
376
|
+
def hfev3_mk_track(cyl: int, head: int, track_v1: HFETrack) -> HFETrack:
|
|
377
|
+
|
|
378
|
+
def add_weak(weak: List[HFEv3_Range], pos: int, nr: int) -> None:
|
|
379
|
+
if len(weak) != 0:
|
|
380
|
+
w = weak[-1]
|
|
381
|
+
if w.e == pos:
|
|
382
|
+
w.n += nr
|
|
383
|
+
return
|
|
384
|
+
weak.append(HFEv3_Range(pos, nr))
|
|
385
|
+
|
|
386
|
+
# Input: Raw HFE
|
|
387
|
+
tdat = track_v1.track.bits.tobytes()
|
|
388
|
+
|
|
389
|
+
# Outputs: Bitcells, bitcell timings, weak areas.
|
|
390
|
+
bits = bitarray(endian='big')
|
|
391
|
+
ticks: List[int] = []
|
|
392
|
+
weak: List[HFEv3_Range] = []
|
|
393
|
+
index: List[int] = []
|
|
394
|
+
|
|
395
|
+
i = rate = 0
|
|
396
|
+
while i < len(tdat):
|
|
397
|
+
x, i = tdat[i], i+1
|
|
398
|
+
if x == HFEv3_Op.Nop:
|
|
399
|
+
pass
|
|
400
|
+
elif x == HFEv3_Op.Index:
|
|
401
|
+
index.append(len(bits))
|
|
402
|
+
elif x == HFEv3_Op.Bitrate:
|
|
403
|
+
if i+1 > len(tdat):
|
|
404
|
+
# Non fatal: This has been observed in HFEv3 images created
|
|
405
|
+
# by HxC tools (see issue #346).
|
|
406
|
+
print(f'T{cyl}.{head}: HFEv3: Truncated bitrate opcode')
|
|
407
|
+
break
|
|
408
|
+
rate, i = tdat[i], i+1
|
|
409
|
+
elif x == HFEv3_Op.SkipBits:
|
|
410
|
+
error.check(i+2 <= len(tdat),
|
|
411
|
+
f'T{cyl}.{head}: HFEv3: Truncated skipbits opcode')
|
|
412
|
+
nr, x, i = tdat[i], tdat[i+1], i+2
|
|
413
|
+
error.check(0 < nr < 8,
|
|
414
|
+
f'T{cyl}.{head}: HFEv3: Bad skipbits value: {nr}')
|
|
415
|
+
if x == HFEv3_Op.Rand:
|
|
416
|
+
add_weak(weak, len(bits), 8-nr)
|
|
417
|
+
x = 0
|
|
418
|
+
try:
|
|
419
|
+
bits.frombytes(bytes([x << nr]))
|
|
420
|
+
except ValueError:
|
|
421
|
+
raise error.Fatal(f'T{cyl}.{head}: HFEv3: Bad skipbits: '
|
|
422
|
+
f'0x{x:02x}<<{nr} = 0x{x<<nr:04x}')
|
|
423
|
+
bits = bits[:-nr]
|
|
424
|
+
ticks += [rate]*(8-nr)
|
|
425
|
+
elif x == HFEv3_Op.Rand:
|
|
426
|
+
add_weak(weak, len(bits), 8)
|
|
427
|
+
bits.frombytes(bytes(1))
|
|
428
|
+
ticks += [rate]*8
|
|
429
|
+
else:
|
|
430
|
+
error.check((x & 0xf0) != 0xf0,
|
|
431
|
+
f'T{cyl}.{head}: HFEv3: unrecognised opcode {x:02x}')
|
|
432
|
+
bits.frombytes(bytes([x]))
|
|
433
|
+
ticks += [rate]*8
|
|
434
|
+
|
|
435
|
+
# If the track did not start with a Bitrate opcode, cycle the rate round
|
|
436
|
+
# from the end of the track.
|
|
437
|
+
error.check(rate != 0, 'HFEv3: Bitrate was never set in track')
|
|
438
|
+
for i in range(len(ticks)):
|
|
439
|
+
if ticks[i] != 0: break
|
|
440
|
+
ticks[i] = rate
|
|
441
|
+
|
|
442
|
+
# This only works if the track is index aligned, with the first sector
|
|
443
|
+
# starting at bit 0.
|
|
444
|
+
hardsector_bits: Optional[List[int]] = None
|
|
445
|
+
if len(index) > 1:
|
|
446
|
+
pos = 0
|
|
447
|
+
hardsector_bits = []
|
|
448
|
+
for x in index[1:]:
|
|
449
|
+
hardsector_bits.append(x-pos)
|
|
450
|
+
pos = x
|
|
451
|
+
# The last index pulse is the pre-index hole: extend to end of track
|
|
452
|
+
hardsector_bits[-1] += len(bits) - pos
|
|
453
|
+
|
|
454
|
+
mt = MasterTrack(
|
|
455
|
+
bits = bits,
|
|
456
|
+
time_per_rev = sum(ticks)/36e6,
|
|
457
|
+
bit_ticks = cast(List[float], ticks), # mypy
|
|
458
|
+
weak = list(map(lambda x: (x.s, x.n), weak)),
|
|
459
|
+
hardsector_bits = hardsector_bits
|
|
460
|
+
)
|
|
461
|
+
return HFETrack(mt)
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
###
|
|
465
|
+
### HFE V3 TRACK GENERATOR
|
|
466
|
+
###
|
|
467
|
+
|
|
468
|
+
# HFEv3_Chunk: Represents a consecutive range of input bitcells with identical
|
|
469
|
+
# bitcell timings and weakness property.
|
|
470
|
+
class HFEv3_Chunk:
|
|
471
|
+
def __init__(self, nbits: int, time_per_bit: float, is_random: bool,
|
|
472
|
+
emit_index: bool):
|
|
473
|
+
self.nbits = nbits
|
|
474
|
+
self.time_per_bit = time_per_bit
|
|
475
|
+
self.is_random = is_random
|
|
476
|
+
self.emit_index = emit_index
|
|
477
|
+
def __str__(self) -> str:
|
|
478
|
+
s = "%d bits, %.4fus per bit" % (self.nbits, self.time_per_bit*1e6)
|
|
479
|
+
if self.is_random:
|
|
480
|
+
s += ", random"
|
|
481
|
+
return s
|
|
482
|
+
|
|
483
|
+
class HFEv3_Generator:
|
|
484
|
+
def __init__(self, track: MasterTrack) -> None:
|
|
485
|
+
# Properties of the input track.
|
|
486
|
+
self.track = track
|
|
487
|
+
if track.bit_ticks is not None:
|
|
488
|
+
self.ticks_per_rev = sum(track.bit_ticks)
|
|
489
|
+
else:
|
|
490
|
+
self.ticks_per_rev = len(track.bits)
|
|
491
|
+
self.time_per_tick = track.time_per_rev / self.ticks_per_rev
|
|
492
|
+
# index_positions: Bit positions at which to insert index pulses
|
|
493
|
+
index_positions = track.hardsector_bits
|
|
494
|
+
if not index_positions:
|
|
495
|
+
index_positions = [ 0 ]
|
|
496
|
+
else:
|
|
497
|
+
index_positions[-1] //= 2
|
|
498
|
+
index_positions = list(it.accumulate([ 0 ] + index_positions))
|
|
499
|
+
index_positions.append(len(track.bits))
|
|
500
|
+
self.index_positions = index_positions
|
|
501
|
+
# tick_iter: An iterator over ranges of consecutive bitcells with
|
|
502
|
+
# identical ticks per bitcell.
|
|
503
|
+
self.ticks: List[Tuple[int,int,float]]
|
|
504
|
+
if track.bit_ticks is None:
|
|
505
|
+
self.ticks = [(0,len(track.bits),1)]
|
|
506
|
+
else:
|
|
507
|
+
self.ticks = []
|
|
508
|
+
c, s = track.bit_ticks[0], 0
|
|
509
|
+
for i, t in enumerate(track.bit_ticks):
|
|
510
|
+
if t != c:
|
|
511
|
+
self.ticks.append((s, i-s, c))
|
|
512
|
+
c, s = t, i
|
|
513
|
+
self.ticks.append((s, len(track.bit_ticks)-s, c))
|
|
514
|
+
self.tick_iter = map(lambda x: HFEv3_Range(*x),
|
|
515
|
+
it.chain(self.ticks, [(len(track.bits),0,0)]))
|
|
516
|
+
self.tick_cur = next(self.tick_iter)
|
|
517
|
+
# weak_iter: An iterator over weak ranges.
|
|
518
|
+
self.weak_iter = map(lambda x: HFEv3_Range(*x),
|
|
519
|
+
it.chain(track.weak, [(len(track.bits),0)]))
|
|
520
|
+
self.weak_cur = next(self.weak_iter)
|
|
521
|
+
# out: The raw HFEv3 output bytestream that we are generating.
|
|
522
|
+
self.out = bytearray()
|
|
523
|
+
# time: Input track current time, in seconds.
|
|
524
|
+
# hfe_time: HFE track output current time, in seconds.
|
|
525
|
+
self.time = self.hfe_time = 0.0
|
|
526
|
+
# pos: Input track current position, in bitcells.
|
|
527
|
+
self.pos = 0
|
|
528
|
+
# rate: Current HFE output rate, as set by HFEv3_Op.Bitrate.
|
|
529
|
+
# rate_change_pos: Byte position we last updated rate in HFE output.
|
|
530
|
+
self.rate, self.rate_change_pos = -1, 0
|
|
531
|
+
# sec: Which sector are we emitting
|
|
532
|
+
self.sec = 0
|
|
533
|
+
# chunk: Chunk of input bitcells currently being processed.
|
|
534
|
+
self.chunk = self.next_chunk()
|
|
535
|
+
|
|
536
|
+
def next_chunk(self) -> Optional[HFEv3_Chunk]:
|
|
537
|
+
# All done? Then return nothing.
|
|
538
|
+
if self.pos >= len(self.track.bits):
|
|
539
|
+
return None
|
|
540
|
+
# Position to next sector pulse
|
|
541
|
+
emit_index = False
|
|
542
|
+
while (n := self.index_positions[self.sec] - self.pos) <= 0:
|
|
543
|
+
assert n == 0 and not emit_index
|
|
544
|
+
self.sec += 1
|
|
545
|
+
emit_index = True
|
|
546
|
+
# Position among bitcells with identical timing.
|
|
547
|
+
while self.pos >= self.tick_cur.e:
|
|
548
|
+
self.tick_cur = next(self.tick_iter)
|
|
549
|
+
assert self.pos >= self.tick_cur.s
|
|
550
|
+
n = min(n, self.tick_cur.e - self.pos)
|
|
551
|
+
# Position relative to weak ranges.
|
|
552
|
+
while self.pos >= self.weak_cur.e:
|
|
553
|
+
self.weak_cur = next(self.weak_iter)
|
|
554
|
+
if self.pos < self.weak_cur.s:
|
|
555
|
+
n = min(n, self.weak_cur.s - self.pos)
|
|
556
|
+
is_random = False
|
|
557
|
+
else:
|
|
558
|
+
n = min(n, self.weak_cur.e - self.pos)
|
|
559
|
+
is_random = True
|
|
560
|
+
return HFEv3_Chunk(n, self.time_per_tick * self.tick_cur.val,
|
|
561
|
+
is_random, emit_index)
|
|
562
|
+
|
|
563
|
+
def increment_position(self, n: int) -> None:
|
|
564
|
+
c = self.chunk
|
|
565
|
+
assert c is not None
|
|
566
|
+
self.pos += n
|
|
567
|
+
self.time += n * c.time_per_bit
|
|
568
|
+
self.hfe_time += n * self.rate/36e6
|
|
569
|
+
c.nbits -= n
|
|
570
|
+
if c.nbits == 0:
|
|
571
|
+
self.chunk = self.next_chunk()
|
|
572
|
+
|
|
573
|
+
def raw_hfe_bytes(self) -> bytes:
|
|
574
|
+
x = bitarray(endian='big')
|
|
575
|
+
x.frombytes(self.out)
|
|
576
|
+
x.bytereverse()
|
|
577
|
+
return x.tobytes()
|
|
578
|
+
|
|
579
|
+
@classmethod
|
|
580
|
+
def empty(cls, time_per_rev: float, bitrate: float) -> HFEv3_Generator:
|
|
581
|
+
nbits = round(2000 * bitrate * time_per_rev)
|
|
582
|
+
return cls(MasterTrack(
|
|
583
|
+
bits = bitarray(nbits),
|
|
584
|
+
time_per_rev = time_per_rev,
|
|
585
|
+
weak = [(0, nbits)]))
|
|
586
|
+
|
|
587
|
+
|
|
588
|
+
def hfev3_get_image(hfe: HFE) -> bytes:
|
|
589
|
+
|
|
590
|
+
n_side = 1
|
|
591
|
+
n_cyl = max(hfe.to_track.keys(), default=(0,), key=lambda x:x[0])[0]
|
|
592
|
+
n_cyl += 1
|
|
593
|
+
|
|
594
|
+
# We dynamically build the Track-LUT and -Data arrays.
|
|
595
|
+
tlut = bytearray()
|
|
596
|
+
tdat = bytearray()
|
|
597
|
+
|
|
598
|
+
try:
|
|
599
|
+
default_time_per_rev = (next(iter(hfe.to_track.values()))
|
|
600
|
+
.track.time_per_rev)
|
|
601
|
+
except StopIteration:
|
|
602
|
+
default_time_per_rev = 0.2
|
|
603
|
+
|
|
604
|
+
bitrate = hfe.opts.bitrate
|
|
605
|
+
assert bitrate is not None
|
|
606
|
+
|
|
607
|
+
# Stuff real data into the image.
|
|
608
|
+
for cyl in range(n_cyl):
|
|
609
|
+
time_per_rev = (hfe.to_track[cyl,0].track.time_per_rev
|
|
610
|
+
if (cyl,0) in hfe.to_track
|
|
611
|
+
else hfe.to_track[cyl,1].track.time_per_rev
|
|
612
|
+
if (cyl,1) in hfe.to_track
|
|
613
|
+
else default_time_per_rev)
|
|
614
|
+
s = list()
|
|
615
|
+
s.append(HFEv3_Generator(hfe.to_track[cyl,0].track)
|
|
616
|
+
if (cyl,0) in hfe.to_track
|
|
617
|
+
else HFEv3_Generator.empty(time_per_rev, bitrate))
|
|
618
|
+
s.append(HFEv3_Generator(hfe.to_track[cyl,1].track)
|
|
619
|
+
if (cyl,1) in hfe.to_track
|
|
620
|
+
else HFEv3_Generator.empty(time_per_rev, bitrate))
|
|
621
|
+
|
|
622
|
+
if (cyl,1) in hfe.to_track:
|
|
623
|
+
n_side = 2
|
|
624
|
+
|
|
625
|
+
max_skew, max_delta = 0.0, 0.0
|
|
626
|
+
while True:
|
|
627
|
+
# Select the track 'x' with work to do and shortest output buffer.
|
|
628
|
+
x, y, c = s[0], s[1], s[0].chunk
|
|
629
|
+
if c is None or (len(y.out) < len(x.out) and y.chunk is not None):
|
|
630
|
+
x, y, c = y, x, y.chunk
|
|
631
|
+
if c is None:
|
|
632
|
+
break
|
|
633
|
+
|
|
634
|
+
# Calculate timing error across drive heads, in bitcells.
|
|
635
|
+
# This also accounts for differences in output byte position.
|
|
636
|
+
diff = (round((x.time - y.time) / c.time_per_bit)
|
|
637
|
+
+ (len(y.out) - len(x.out)) * 8)
|
|
638
|
+
max_skew = max(max_skew, abs(diff))
|
|
639
|
+
|
|
640
|
+
# Adjust time per bit for accumulated error in time due to
|
|
641
|
+
# rounding error in the HFEv3_Op.Bitrate parameter.
|
|
642
|
+
# Note that we distribute the correction factor across the
|
|
643
|
+
# worst-case number of bitcells before we allow ourselves
|
|
644
|
+
# another minor bitrate adjustment.
|
|
645
|
+
rate_distance = 64 # byte-cells
|
|
646
|
+
tpb = c.time_per_bit + (x.time - x.hfe_time) / (rate_distance*8)
|
|
647
|
+
|
|
648
|
+
if c.emit_index:
|
|
649
|
+
c.emit_index = False
|
|
650
|
+
x.out.append(HFEv3_Op.Index)
|
|
651
|
+
diff -= 8
|
|
652
|
+
|
|
653
|
+
# Do a rate change if the rate has significantly changed or,
|
|
654
|
+
# for a change of +/-1, if we haven't changed rate in a while.
|
|
655
|
+
rate = round(tpb * 36e6)
|
|
656
|
+
if (rate != x.rate
|
|
657
|
+
and (abs(rate-x.rate) > 1 or diff >= 16
|
|
658
|
+
or (len(x.out) - x.rate_change_pos) > rate_distance)):
|
|
659
|
+
x.out.append(HFEv3_Op.Bitrate)
|
|
660
|
+
x.out.append(rate)
|
|
661
|
+
x.rate = rate
|
|
662
|
+
x.rate_change_pos = len(x.out)
|
|
663
|
+
diff -= 16
|
|
664
|
+
|
|
665
|
+
# Insert Nop padding if we still need it.
|
|
666
|
+
if diff >= 8:
|
|
667
|
+
x.out.append(HFEv3_Op.Nop)
|
|
668
|
+
|
|
669
|
+
# Emit up to 8 bitcells.
|
|
670
|
+
n = min(c.nbits, 8)
|
|
671
|
+
if n < 8:
|
|
672
|
+
x.out.append(HFEv3_Op.SkipBits)
|
|
673
|
+
x.out.append(8 - n)
|
|
674
|
+
if c.is_random:
|
|
675
|
+
x.out.append(HFEv3_Op.Rand)
|
|
676
|
+
else:
|
|
677
|
+
# Extract next bitcells into a stream-ready byte.
|
|
678
|
+
b = x.track.bits[x.pos:x.pos+n].tobytes()[0] >> (8-n)
|
|
679
|
+
# If the byte looks like an opcode, skip a bit.
|
|
680
|
+
if (b & 0xf0) == 0xf0:
|
|
681
|
+
n = 7
|
|
682
|
+
b >>= 1
|
|
683
|
+
x.out.append(HFEv3_Op.SkipBits)
|
|
684
|
+
x.out.append(8 - n)
|
|
685
|
+
# Emit the fixed-up byte.
|
|
686
|
+
x.out.append(b)
|
|
687
|
+
|
|
688
|
+
# Update tallies.
|
|
689
|
+
x.increment_position(n)
|
|
690
|
+
max_delta = max(max_delta, abs(x.time - x.hfe_time))
|
|
691
|
+
|
|
692
|
+
info = 'HFEv3 [%d] ' % cyl
|
|
693
|
+
for i,x in enumerate(s):
|
|
694
|
+
delta = len(x.out)-len(x.track.bits)//8
|
|
695
|
+
info += ('h%d:%d/+%d/+%.02f%% ' %
|
|
696
|
+
(i, len(x.out), delta, delta*8*100/len(x.track.bits)))
|
|
697
|
+
info += 'hskew:%dbc rate-err:%.02fus' % (max_skew, max_delta*1e6)
|
|
698
|
+
print(info)
|
|
699
|
+
|
|
700
|
+
nr_bytes = max(len(x.out) for x in s)
|
|
701
|
+
error.check(
|
|
702
|
+
nr_bytes < 32768,
|
|
703
|
+
'''\
|
|
704
|
+
HFEv3: Track too long to fit in image!
|
|
705
|
+
Are you trying to convert raw flux (SCP, KF, etc)?
|
|
706
|
+
If so: Try specifying 'uniform': eg. name.hfe::version=3:uniform
|
|
707
|
+
(will break variable-rate copy protections such as Copylock)
|
|
708
|
+
If not: Report a bug.''')
|
|
709
|
+
|
|
710
|
+
nr_blocks = (nr_bytes + 0xff) // 0x100
|
|
711
|
+
for x in s:
|
|
712
|
+
x.out += bytes([HFEv3_Op.Nop]) * (nr_blocks*0x100 - len(x.out))
|
|
713
|
+
bc = [x.raw_hfe_bytes() for x in s]
|
|
714
|
+
tlut += struct.pack("<2H", len(tdat)//512 + 2, 2 * nr_bytes)
|
|
715
|
+
for b in range(nr_blocks):
|
|
716
|
+
for t in bc:
|
|
717
|
+
tdat += t[b*256:(b+1)*256]
|
|
718
|
+
|
|
719
|
+
# Construct the image header.
|
|
720
|
+
header = struct.pack("<8s4B2H2BH2B",
|
|
721
|
+
b"HXCHFEV3",
|
|
722
|
+
0,
|
|
723
|
+
n_cyl,
|
|
724
|
+
n_side,
|
|
725
|
+
hfe.opts.encoding,
|
|
726
|
+
hfe.opts.bitrate,
|
|
727
|
+
0, # rpm (unused)
|
|
728
|
+
hfe.opts.interface,
|
|
729
|
+
1, # rsvd
|
|
730
|
+
1, # track list offset
|
|
731
|
+
0xff, # write_allowed
|
|
732
|
+
0xff if not hfe.opts.double_step else 0)
|
|
733
|
+
|
|
734
|
+
# Pad the header and TLUT to 512-byte blocks.
|
|
735
|
+
header += bytes([0xff] * (0x200 - len(header)))
|
|
736
|
+
tlut += bytes([0xff] * (0x200 - len(tlut)))
|
|
737
|
+
|
|
738
|
+
return header + tlut + tdat
|
|
739
|
+
|
|
740
|
+
# Local variables:
|
|
741
|
+
# python-indent: 4
|
|
742
|
+
# End:
|