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,362 @@
|
|
|
1
|
+
# greaseweazle/codec/codec.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 Dict, List, Tuple, Optional
|
|
10
|
+
|
|
11
|
+
import os.path, re
|
|
12
|
+
import importlib.resources
|
|
13
|
+
from copy import copy
|
|
14
|
+
from abc import abstractmethod
|
|
15
|
+
|
|
16
|
+
from diskstack._vendor.greaseweazle import error
|
|
17
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
18
|
+
from diskstack._vendor.greaseweazle.tools import util
|
|
19
|
+
from diskstack._vendor.greaseweazle.track import MasterTrack, PLL
|
|
20
|
+
from diskstack._vendor.greaseweazle.flux import Flux, HasFlux, WriteoutFlux
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class Codec:
|
|
24
|
+
|
|
25
|
+
@property
|
|
26
|
+
@abstractmethod
|
|
27
|
+
def nsec(self) -> int:
|
|
28
|
+
...
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def summary_string(self) -> str:
|
|
32
|
+
...
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def has_sec(self, sec_id: int) -> bool:
|
|
36
|
+
...
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def nr_missing(self) -> int:
|
|
40
|
+
...
|
|
41
|
+
|
|
42
|
+
@abstractmethod
|
|
43
|
+
def get_img_track(self) -> bytearray:
|
|
44
|
+
...
|
|
45
|
+
|
|
46
|
+
@abstractmethod
|
|
47
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
48
|
+
...
|
|
49
|
+
|
|
50
|
+
@abstractmethod
|
|
51
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL] = None) -> None:
|
|
52
|
+
...
|
|
53
|
+
|
|
54
|
+
@abstractmethod
|
|
55
|
+
def master_track(self) -> MasterTrack:
|
|
56
|
+
...
|
|
57
|
+
|
|
58
|
+
def flux(self) -> Flux:
|
|
59
|
+
return self.master_track().flux()
|
|
60
|
+
|
|
61
|
+
def flux_for_writeout(self, cue_at_index) -> WriteoutFlux:
|
|
62
|
+
return self.master_track().flux_for_writeout(cue_at_index)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
class TrackDef:
|
|
66
|
+
|
|
67
|
+
default_revs: float
|
|
68
|
+
|
|
69
|
+
@abstractmethod
|
|
70
|
+
def add_param(self, key: str, val) -> None:
|
|
71
|
+
...
|
|
72
|
+
|
|
73
|
+
@abstractmethod
|
|
74
|
+
def finalise(self) -> None:
|
|
75
|
+
...
|
|
76
|
+
|
|
77
|
+
@abstractmethod
|
|
78
|
+
def mk_track(self, cyl: int, head: int) -> codec.Codec:
|
|
79
|
+
...
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
class DiskDef:
|
|
83
|
+
|
|
84
|
+
def __init__(self) -> None:
|
|
85
|
+
self.cyls: Optional[int] = None
|
|
86
|
+
self.heads: Optional[int] = None
|
|
87
|
+
self.track_map: Dict[Tuple[int,int],TrackDef] = dict()
|
|
88
|
+
|
|
89
|
+
def add_param(self, key: str, val: str) -> None:
|
|
90
|
+
if key == 'cyls':
|
|
91
|
+
n = int(val)
|
|
92
|
+
error.check(1 <= n <= 255, '%s out of range' % key)
|
|
93
|
+
self.cyls = n
|
|
94
|
+
elif key == 'heads':
|
|
95
|
+
n = int(val)
|
|
96
|
+
error.check(1 <= n <= 2, '%s out of range' % key)
|
|
97
|
+
self.heads = n
|
|
98
|
+
else:
|
|
99
|
+
raise error.Fatal('unrecognised disk option: %s' % key)
|
|
100
|
+
|
|
101
|
+
def finalise(self):
|
|
102
|
+
error.check(self.cyls is not None, 'missing cyls')
|
|
103
|
+
error.check(self.heads is not None, 'missing heads')
|
|
104
|
+
self.tracks = util.TrackSet(self.trackset())
|
|
105
|
+
|
|
106
|
+
def trackset(self):
|
|
107
|
+
s = 'c=0'
|
|
108
|
+
if self.cyls > 1:
|
|
109
|
+
s += '-' + str(self.cyls-1)
|
|
110
|
+
s += ':h=0'
|
|
111
|
+
if self.heads > 1:
|
|
112
|
+
s += '-' + str(self.heads-1)
|
|
113
|
+
return s
|
|
114
|
+
|
|
115
|
+
def mk_track(self, cyl: int, head: int) -> Optional[codec.Codec]:
|
|
116
|
+
if (cyl, head) not in self.track_map:
|
|
117
|
+
return None
|
|
118
|
+
return self.track_map[cyl, head].mk_track(cyl, head)
|
|
119
|
+
|
|
120
|
+
def decode_flux(self, cyl: int, head: int,
|
|
121
|
+
track: HasFlux) -> Optional[codec.Codec]:
|
|
122
|
+
t = self.mk_track(cyl, head)
|
|
123
|
+
if t is not None:
|
|
124
|
+
t.decode_flux(track)
|
|
125
|
+
return t
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def default_revs(self) -> float:
|
|
129
|
+
return max([x.default_revs for x in self.track_map.values()])
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
class DiskDef_File:
|
|
133
|
+
def __init__(self, name: Optional[str],
|
|
134
|
+
parent: Optional[DiskDef_File] = None) -> None:
|
|
135
|
+
self.path: Optional[str] = None
|
|
136
|
+
self.name: str = 'diskdefs.cfg' if name is None else name
|
|
137
|
+
if name is None or (parent and not parent.path):
|
|
138
|
+
res = importlib.resources.files(
|
|
139
|
+
'diskstack._vendor.greaseweazle.data')
|
|
140
|
+
with res.joinpath(self.name).open('r') as f:
|
|
141
|
+
self.lines = f.readlines()
|
|
142
|
+
else:
|
|
143
|
+
if parent:
|
|
144
|
+
assert parent.path # mypy
|
|
145
|
+
self.path = os.path.join(os.path.dirname(parent.path),
|
|
146
|
+
self.name)
|
|
147
|
+
else:
|
|
148
|
+
self.path = os.path.expanduser(self.name)
|
|
149
|
+
with open(self.path, 'r') as f:
|
|
150
|
+
self.lines = f.readlines()
|
|
151
|
+
|
|
152
|
+
|
|
153
|
+
# Import the TrackDef subclasses
|
|
154
|
+
from diskstack._vendor.greaseweazle.codec import bitcell
|
|
155
|
+
from diskstack._vendor.greaseweazle.codec.ibm import ibm
|
|
156
|
+
from diskstack._vendor.greaseweazle.codec.amiga import amigados
|
|
157
|
+
from diskstack._vendor.greaseweazle.codec.macintosh import mac_gcr
|
|
158
|
+
from diskstack._vendor.greaseweazle.codec.commodore import c64_gcr
|
|
159
|
+
from diskstack._vendor.greaseweazle.codec.apple2 import apple2_gcr
|
|
160
|
+
from diskstack._vendor.greaseweazle.codec.hp import hp_mmfm
|
|
161
|
+
from diskstack._vendor.greaseweazle.codec.northstar import northstar
|
|
162
|
+
from diskstack._vendor.greaseweazle.codec.micropolis import micropolis
|
|
163
|
+
from diskstack._vendor.greaseweazle.codec.datageneral import datageneral
|
|
164
|
+
|
|
165
|
+
def mk_trackdef(format_name: str) -> TrackDef:
|
|
166
|
+
if format_name in ['amiga.amigados']:
|
|
167
|
+
return amigados.AmigaDOSDef(format_name)
|
|
168
|
+
if format_name in ['ibm.mfm', 'ibm.fm', 'dec.rx02']:
|
|
169
|
+
return ibm.IBMTrack_FixedDef(format_name)
|
|
170
|
+
if format_name in ['ibm.scan']:
|
|
171
|
+
return ibm.IBMTrack_ScanDef(format_name)
|
|
172
|
+
if format_name in ['mac.gcr']:
|
|
173
|
+
return mac_gcr.MacGCRDef(format_name)
|
|
174
|
+
if format_name in ['c64.gcr']:
|
|
175
|
+
return c64_gcr.C64GCRDef(format_name)
|
|
176
|
+
if format_name in ['hp.mmfm']:
|
|
177
|
+
return hp_mmfm.HPMMFMDef(format_name)
|
|
178
|
+
if format_name in ['northstar']:
|
|
179
|
+
return northstar.NorthStarDef(format_name)
|
|
180
|
+
if format_name in ['micropolis']:
|
|
181
|
+
return micropolis.MicropolisDef(format_name)
|
|
182
|
+
if format_name in ['apple2.gcr']:
|
|
183
|
+
return apple2_gcr.Apple2GCRDef(format_name)
|
|
184
|
+
if format_name in ['bitcell']:
|
|
185
|
+
return bitcell.BitcellTrackDef(format_name)
|
|
186
|
+
if format_name in ['datageneral']:
|
|
187
|
+
return datageneral.DataGeneralDef(format_name)
|
|
188
|
+
raise error.Fatal('unrecognised format name: %s' % format_name)
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
class ParseMode:
|
|
192
|
+
Outer = 0
|
|
193
|
+
Disk = 1
|
|
194
|
+
Track = 2
|
|
195
|
+
|
|
196
|
+
def _get_diskdef(
|
|
197
|
+
format_name: str,
|
|
198
|
+
prefix: str,
|
|
199
|
+
diskdef_file: DiskDef_File
|
|
200
|
+
) -> Optional[DiskDef]:
|
|
201
|
+
|
|
202
|
+
parse_mode = ParseMode.Outer
|
|
203
|
+
active = False
|
|
204
|
+
disk: Optional[DiskDef] = None
|
|
205
|
+
track: Optional[TrackDef] = None
|
|
206
|
+
|
|
207
|
+
for linenr, l in enumerate(diskdef_file.lines, start=1):
|
|
208
|
+
try:
|
|
209
|
+
# Strip comments and whitespace.
|
|
210
|
+
match = re.match(r'\s*([^#]*)', l)
|
|
211
|
+
assert match is not None # mypy
|
|
212
|
+
t = match.group(1).strip()
|
|
213
|
+
|
|
214
|
+
# Skip empty lines.
|
|
215
|
+
if not t:
|
|
216
|
+
continue
|
|
217
|
+
|
|
218
|
+
if parse_mode == ParseMode.Outer:
|
|
219
|
+
disk_match = re.match(r'disk\s+([\w,.-]+)', t)
|
|
220
|
+
if disk_match:
|
|
221
|
+
parse_mode = ParseMode.Disk
|
|
222
|
+
active = ((prefix + disk_match.group(1).casefold())
|
|
223
|
+
== format_name)
|
|
224
|
+
if active:
|
|
225
|
+
disk = DiskDef()
|
|
226
|
+
else:
|
|
227
|
+
import_match = re.match(r'import\s+([\w,.-]*)\s*"([^"]+)"',
|
|
228
|
+
t)
|
|
229
|
+
error.check(import_match is not None, 'syntax error')
|
|
230
|
+
assert import_match is not None # mypy
|
|
231
|
+
sub_prefix = prefix + import_match.group(1).casefold()
|
|
232
|
+
if format_name.startswith(sub_prefix):
|
|
233
|
+
disk = _get_diskdef(format_name, sub_prefix,
|
|
234
|
+
DiskDef_File(
|
|
235
|
+
name = import_match.group(2),
|
|
236
|
+
parent = diskdef_file))
|
|
237
|
+
if disk:
|
|
238
|
+
break
|
|
239
|
+
|
|
240
|
+
elif parse_mode == ParseMode.Disk:
|
|
241
|
+
if t == 'end':
|
|
242
|
+
parse_mode = ParseMode.Outer
|
|
243
|
+
active = False
|
|
244
|
+
if disk:
|
|
245
|
+
break
|
|
246
|
+
continue
|
|
247
|
+
tracks_match = re.match(r'tracks\s+([0-9,.*-]+)'
|
|
248
|
+
r'\s+([\w,.-]+)', t)
|
|
249
|
+
if tracks_match:
|
|
250
|
+
parse_mode = ParseMode.Track
|
|
251
|
+
if not active:
|
|
252
|
+
continue
|
|
253
|
+
assert disk is not None # mypy
|
|
254
|
+
error.check(disk.cyls is not None, 'missing cyls')
|
|
255
|
+
error.check(disk.heads is not None, 'missing heads')
|
|
256
|
+
assert disk.cyls is not None # mypy
|
|
257
|
+
assert disk.heads is not None # mypy
|
|
258
|
+
track = mk_trackdef(tracks_match.group(2))
|
|
259
|
+
for x in tracks_match.group(1).split(','):
|
|
260
|
+
if x == '*':
|
|
261
|
+
for c in range(disk.cyls):
|
|
262
|
+
for hd in range(disk.heads):
|
|
263
|
+
if (c,hd) not in disk.track_map:
|
|
264
|
+
disk.track_map[c,hd] = track
|
|
265
|
+
else:
|
|
266
|
+
t_match = re.match(r'(\d+)(?:-(\d+))?'
|
|
267
|
+
r'(?:\.([01]))?', x)
|
|
268
|
+
error.check(t_match is not None,
|
|
269
|
+
'bad track specifier')
|
|
270
|
+
assert t_match is not None # mypy
|
|
271
|
+
s = int(t_match.group(1))
|
|
272
|
+
e = t_match.group(2)
|
|
273
|
+
e = s if e is None else int(e)
|
|
274
|
+
h = t_match.group(3)
|
|
275
|
+
if h is None:
|
|
276
|
+
h = list(range(disk.heads))
|
|
277
|
+
else:
|
|
278
|
+
error.check(int(h) < disk.heads,
|
|
279
|
+
'head out of range')
|
|
280
|
+
h = [int(h)]
|
|
281
|
+
error.check(0 <= s < disk.cyls
|
|
282
|
+
and 0 <= e < disk.cyls
|
|
283
|
+
and s <= e,
|
|
284
|
+
'cylinder out of range')
|
|
285
|
+
for c in range(s,e+1):
|
|
286
|
+
for hd in h:
|
|
287
|
+
disk.track_map[c,hd] = track
|
|
288
|
+
continue
|
|
289
|
+
|
|
290
|
+
if not active:
|
|
291
|
+
continue
|
|
292
|
+
assert disk is not None # mypy
|
|
293
|
+
|
|
294
|
+
keyval_match = re.match(r'([a-zA-Z0-9:,._-]+)\s*='
|
|
295
|
+
r'\s*([a-zA-Z0-9:,._-]+)', t)
|
|
296
|
+
error.check(keyval_match is not None, 'syntax error')
|
|
297
|
+
assert keyval_match is not None # mypy
|
|
298
|
+
disk.add_param(keyval_match.group(1),
|
|
299
|
+
keyval_match.group(2))
|
|
300
|
+
|
|
301
|
+
elif parse_mode == ParseMode.Track:
|
|
302
|
+
if t == 'end':
|
|
303
|
+
parse_mode = ParseMode.Disk
|
|
304
|
+
if track is not None:
|
|
305
|
+
track.finalise()
|
|
306
|
+
track = None
|
|
307
|
+
continue
|
|
308
|
+
|
|
309
|
+
if not active:
|
|
310
|
+
continue
|
|
311
|
+
assert track is not None # mypy
|
|
312
|
+
|
|
313
|
+
keyval_match = re.match(r'([a-zA-Z0-9:,._-]+)\s*='
|
|
314
|
+
r'\s*([a-zA-Z0-9:,._*-]+)', t)
|
|
315
|
+
error.check(keyval_match is not None, 'syntax error')
|
|
316
|
+
assert keyval_match is not None # mypy
|
|
317
|
+
track.add_param(keyval_match.group(1),
|
|
318
|
+
keyval_match.group(2))
|
|
319
|
+
|
|
320
|
+
except Exception as err:
|
|
321
|
+
if err.args and isinstance(x := err.args[0], str):
|
|
322
|
+
ctxt = f'At {diskdef_file.name}, line {linenr}:'
|
|
323
|
+
ctxt += '\n' if x.startswith('At') else ' '
|
|
324
|
+
err.args = (ctxt + x,) + err.args[1:]
|
|
325
|
+
raise
|
|
326
|
+
|
|
327
|
+
return disk
|
|
328
|
+
|
|
329
|
+
def get_diskdef(
|
|
330
|
+
format_name: str,
|
|
331
|
+
diskdef_filename: Optional[str] = None
|
|
332
|
+
) -> Optional[DiskDef]:
|
|
333
|
+
diskdef_file = DiskDef_File(name = diskdef_filename)
|
|
334
|
+
disk = _get_diskdef(format_name.casefold(), '', diskdef_file)
|
|
335
|
+
if disk is None:
|
|
336
|
+
return None
|
|
337
|
+
disk.finalise()
|
|
338
|
+
return disk
|
|
339
|
+
|
|
340
|
+
def get_all_formats(prefix: str, diskdef_file: DiskDef_File) -> List[str]:
|
|
341
|
+
formats = []
|
|
342
|
+
for l in diskdef_file.lines:
|
|
343
|
+
disk_match = re.match(r'\s*disk\s+([\w,.-]+)', l)
|
|
344
|
+
if disk_match:
|
|
345
|
+
formats.append(prefix + disk_match.group(1))
|
|
346
|
+
import_match = re.match(r'\s*import\s+([\w,.-]*)\s*"([^"]+)"', l)
|
|
347
|
+
if import_match:
|
|
348
|
+
formats += get_all_formats(
|
|
349
|
+
prefix + import_match.group(1),
|
|
350
|
+
DiskDef_File(
|
|
351
|
+
name = import_match.group(2),
|
|
352
|
+
parent = diskdef_file))
|
|
353
|
+
return formats
|
|
354
|
+
|
|
355
|
+
def print_formats(diskdef_filename: Optional[str] = None) -> str:
|
|
356
|
+
formats = get_all_formats('', DiskDef_File(name = diskdef_filename))
|
|
357
|
+
formats.sort()
|
|
358
|
+
return util.columnify(formats)
|
|
359
|
+
|
|
360
|
+
# Local variables:
|
|
361
|
+
# python-indent: 4
|
|
362
|
+
# End:
|
|
File without changes
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
# greaseweazle/codec/c64/c64_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
|
+
# GCR 0xffd49 = ...11111 01010 01001 = [sync] A 9 -> [sync] 08 (hdr block id)
|
|
22
|
+
sector_sync = bitarray(endian='big')
|
|
23
|
+
sector_sync.frombytes(b'\xff\xd4\x90')
|
|
24
|
+
sector_sync = sector_sync[:20]
|
|
25
|
+
|
|
26
|
+
# GCR 0xffd57 = ...11111 01010 10111 = [sync] A 17 -> [sync] 07 (data block id)
|
|
27
|
+
data_sync = bitarray(endian='big')
|
|
28
|
+
data_sync.frombytes(b'\xff\xd5\x70')
|
|
29
|
+
data_sync = data_sync[:20]
|
|
30
|
+
|
|
31
|
+
bad_sector = b'-=[BAD SECTOR]=-' * 16
|
|
32
|
+
|
|
33
|
+
class C64GCR(codec.Codec):
|
|
34
|
+
|
|
35
|
+
time_per_rev = 0.2
|
|
36
|
+
|
|
37
|
+
verify_revs = default_revs
|
|
38
|
+
|
|
39
|
+
def __init__(self, cyl: int, head: int, config):
|
|
40
|
+
self.cyl, self.head = cyl, head
|
|
41
|
+
self.config = config
|
|
42
|
+
self.clock = config.clock
|
|
43
|
+
self.sector: List[Optional[bytes]]
|
|
44
|
+
self.sector = [None] * self.nsec
|
|
45
|
+
self.disk_id: Optional[int] = None
|
|
46
|
+
error.check(optimised.enabled,
|
|
47
|
+
'Commodore GCR requires optimised C extension')
|
|
48
|
+
|
|
49
|
+
@property
|
|
50
|
+
def nsec(self) -> int:
|
|
51
|
+
return self.config.secs
|
|
52
|
+
|
|
53
|
+
def summary_string(self) -> str:
|
|
54
|
+
nsec, nbad = self.nsec, self.nr_missing()
|
|
55
|
+
s = "Commodore GCR (%d/%d sectors)" % (nsec - nbad, nsec)
|
|
56
|
+
return s
|
|
57
|
+
|
|
58
|
+
def set_disk_id(self, disk_id: int):
|
|
59
|
+
assert self.disk_id is None
|
|
60
|
+
self.disk_id = disk_id
|
|
61
|
+
|
|
62
|
+
# private
|
|
63
|
+
def add(self, sec_id, data) -> None:
|
|
64
|
+
assert not self.has_sec(sec_id)
|
|
65
|
+
self.sector[sec_id] = data
|
|
66
|
+
|
|
67
|
+
# private
|
|
68
|
+
def tracknr(self) -> int:
|
|
69
|
+
return self.head*35 + self.cyl + 1
|
|
70
|
+
|
|
71
|
+
def has_sec(self, sec_id: int) -> bool:
|
|
72
|
+
return self.sector[sec_id] is not None
|
|
73
|
+
|
|
74
|
+
def nr_missing(self) -> int:
|
|
75
|
+
return len([sec for sec in self.sector if sec is None])
|
|
76
|
+
|
|
77
|
+
def get_img_track(self) -> bytearray:
|
|
78
|
+
tdat = bytearray()
|
|
79
|
+
for sec in self.sector:
|
|
80
|
+
tdat += sec if sec is not None else bad_sector
|
|
81
|
+
return tdat
|
|
82
|
+
|
|
83
|
+
def set_img_track(self, tdat: bytes) -> int:
|
|
84
|
+
totsize = self.nsec * 256
|
|
85
|
+
if len(tdat) < totsize:
|
|
86
|
+
tdat += bytes(totsize - len(tdat))
|
|
87
|
+
for sec in range(self.nsec):
|
|
88
|
+
self.sector[sec] = tdat[sec*256:(sec+1)*256]
|
|
89
|
+
return totsize
|
|
90
|
+
|
|
91
|
+
def decode_flux(self, track: HasFlux, pll: Optional[PLL]=None) -> None:
|
|
92
|
+
raw = PLLTrack(time_per_rev = self.time_per_rev,
|
|
93
|
+
clock = self.clock, data = track, pll = pll,
|
|
94
|
+
lowpass_thresh = 2.5e-6)
|
|
95
|
+
bits, _ = raw.get_all_data()
|
|
96
|
+
|
|
97
|
+
for offs in bits.search(sector_sync):
|
|
98
|
+
|
|
99
|
+
if self.nr_missing() == 0:
|
|
100
|
+
break
|
|
101
|
+
|
|
102
|
+
# Decode header, 8 bytes (=10 bytes GCR):
|
|
103
|
+
# 0x08, csum, sector, track, disk_id[2], gap[2]
|
|
104
|
+
offs += 10
|
|
105
|
+
sec = bits[offs:offs+10*8].tobytes()
|
|
106
|
+
if len(sec) != 10:
|
|
107
|
+
continue
|
|
108
|
+
hdr = optimised.decode_c64_gcr(sec)
|
|
109
|
+
sum = 0
|
|
110
|
+
for x in hdr[1:6]:
|
|
111
|
+
sum ^= x
|
|
112
|
+
if sum != 0:
|
|
113
|
+
continue
|
|
114
|
+
sec_id, cyl, disk_id = struct.unpack('>2BH', hdr[2:6])
|
|
115
|
+
if (cyl != self.tracknr() or sec_id >= self.nsec):
|
|
116
|
+
print('T%d.%d: Ignoring unexpected sector C:%d S:%d ID:%04x'
|
|
117
|
+
% (self.cyl, self.head, cyl, sec_id, disk_id))
|
|
118
|
+
continue
|
|
119
|
+
if self.disk_id is None:
|
|
120
|
+
self.disk_id = disk_id
|
|
121
|
+
elif self.disk_id != disk_id:
|
|
122
|
+
print('T%d.%d: Expected ID %04x in sector C:%d S:%d ID:%04x'
|
|
123
|
+
% (self.cyl, self.head, self.disk_id,
|
|
124
|
+
cyl, sec_id, disk_id))
|
|
125
|
+
continue
|
|
126
|
+
if self.has_sec(sec_id):
|
|
127
|
+
continue
|
|
128
|
+
|
|
129
|
+
# Find data
|
|
130
|
+
offs += 8*8
|
|
131
|
+
dat_offs = list(bits[offs:offs+100*8].search(data_sync))
|
|
132
|
+
if len(dat_offs) != 1:
|
|
133
|
+
continue
|
|
134
|
+
offs += dat_offs[0]
|
|
135
|
+
|
|
136
|
+
# Decode data, 260 bytes (=325 bytes GCR):
|
|
137
|
+
# 0x07, data[256], csum, gap[2]
|
|
138
|
+
offs += 10
|
|
139
|
+
sec = bits[offs:offs+260*10].tobytes()
|
|
140
|
+
if len(sec) != 325:
|
|
141
|
+
continue
|
|
142
|
+
sec = optimised.decode_c64_gcr(sec)
|
|
143
|
+
sum = 0
|
|
144
|
+
for x in sec[1:258]:
|
|
145
|
+
sum ^= x
|
|
146
|
+
if sum != 0:
|
|
147
|
+
continue
|
|
148
|
+
|
|
149
|
+
self.add(sec_id, sec[1:257])
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def master_track(self) -> MasterTrack:
|
|
153
|
+
|
|
154
|
+
# Post-index track gap.
|
|
155
|
+
t = bytes([0x55] * 10)
|
|
156
|
+
|
|
157
|
+
for sec_id in range(self.nsec):
|
|
158
|
+
sector = self.sector[sec_id]
|
|
159
|
+
data = bad_sector if sector is None else sector
|
|
160
|
+
# Header
|
|
161
|
+
disk_id = self.disk_id if self.disk_id is not None else 0
|
|
162
|
+
hdr = struct.pack('>2BH', sec_id, self.tracknr(), disk_id)
|
|
163
|
+
sum = 0
|
|
164
|
+
for x in hdr:
|
|
165
|
+
sum ^= x
|
|
166
|
+
hdr = bytes([0x08, sum]) + hdr + bytes([0x0f, 0x0f])
|
|
167
|
+
t += b'\xff' * 5 # sync
|
|
168
|
+
t += optimised.encode_c64_gcr(hdr)
|
|
169
|
+
t += b'\x55' * 9 # gap
|
|
170
|
+
# Data
|
|
171
|
+
t += b'\xff' * 5 # sync
|
|
172
|
+
sum = 0
|
|
173
|
+
for x in data:
|
|
174
|
+
sum ^= x
|
|
175
|
+
t += optimised.encode_c64_gcr(
|
|
176
|
+
bytes([0x07]) + data + bytes([sum, 0x0f, 0x0f]))
|
|
177
|
+
t += b'\x55' * 9 # gap
|
|
178
|
+
|
|
179
|
+
# Add the pre-index gap.
|
|
180
|
+
tlen = int((self.time_per_rev / self.clock)) & ~31
|
|
181
|
+
t += bytes([0x55] * (tlen//8-len(t)))
|
|
182
|
+
|
|
183
|
+
track = MasterTrack(bits = t, time_per_rev = self.time_per_rev)
|
|
184
|
+
track.verify = self
|
|
185
|
+
return track
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
def verify_track(self, flux):
|
|
189
|
+
readback_track = self.__class__(self.cyl, self.head, self.config)
|
|
190
|
+
readback_track.decode_flux(flux)
|
|
191
|
+
return (readback_track.nr_missing() == 0
|
|
192
|
+
and self.sector == readback_track.sector)
|
|
193
|
+
|
|
194
|
+
|
|
195
|
+
class C64GCRDef(codec.TrackDef):
|
|
196
|
+
|
|
197
|
+
default_revs = default_revs
|
|
198
|
+
|
|
199
|
+
def __init__(self, format_name: str):
|
|
200
|
+
self.secs: Optional[int] = None
|
|
201
|
+
self.clock: Optional[float] = None
|
|
202
|
+
self.finalised = False
|
|
203
|
+
|
|
204
|
+
def add_param(self, key: str, val) -> None:
|
|
205
|
+
if key == 'secs':
|
|
206
|
+
val = int(val)
|
|
207
|
+
self.secs = val
|
|
208
|
+
elif key == 'clock':
|
|
209
|
+
val = float(val)
|
|
210
|
+
self.clock = val * 1e-6
|
|
211
|
+
else:
|
|
212
|
+
raise error.Fatal('unrecognised track option %s' % key)
|
|
213
|
+
|
|
214
|
+
def finalise(self) -> None:
|
|
215
|
+
if self.finalised:
|
|
216
|
+
return
|
|
217
|
+
error.check(self.secs is not None,
|
|
218
|
+
'number of sectors not specified')
|
|
219
|
+
error.check(self.clock is not None,
|
|
220
|
+
'clock period not specified')
|
|
221
|
+
self.finalised = True
|
|
222
|
+
|
|
223
|
+
def mk_track(self, cyl: int, head: int) -> C64GCR:
|
|
224
|
+
return C64GCR(cyl, head, self)
|
|
225
|
+
|
|
226
|
+
|
|
227
|
+
# Local variables:
|
|
228
|
+
# python-indent: 4
|
|
229
|
+
# End:
|
|
File without changes
|