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.
Files changed (119) hide show
  1. diskstack/__init__.py +5 -0
  2. diskstack/_vendor/__init__.py +0 -0
  3. diskstack/_vendor/greaseweazle/COPYING +24 -0
  4. diskstack/_vendor/greaseweazle/__init__.py +2 -0
  5. diskstack/_vendor/greaseweazle/codec/__init__.py +0 -0
  6. diskstack/_vendor/greaseweazle/codec/amiga/__init__.py +0 -0
  7. diskstack/_vendor/greaseweazle/codec/amiga/amigados.py +221 -0
  8. diskstack/_vendor/greaseweazle/codec/apple2/__init__.py +0 -0
  9. diskstack/_vendor/greaseweazle/codec/apple2/apple2_gcr.py +223 -0
  10. diskstack/_vendor/greaseweazle/codec/bitcell.py +117 -0
  11. diskstack/_vendor/greaseweazle/codec/codec.py +362 -0
  12. diskstack/_vendor/greaseweazle/codec/commodore/__init__.py +0 -0
  13. diskstack/_vendor/greaseweazle/codec/commodore/c64_gcr.py +229 -0
  14. diskstack/_vendor/greaseweazle/codec/datageneral/__init__.py +0 -0
  15. diskstack/_vendor/greaseweazle/codec/datageneral/datageneral.py +211 -0
  16. diskstack/_vendor/greaseweazle/codec/hp/__init__.py +0 -0
  17. diskstack/_vendor/greaseweazle/codec/hp/hp_mmfm.py +258 -0
  18. diskstack/_vendor/greaseweazle/codec/ibm/__init__.py +0 -0
  19. diskstack/_vendor/greaseweazle/codec/ibm/ibm.py +1005 -0
  20. diskstack/_vendor/greaseweazle/codec/macintosh/__init__.py +0 -0
  21. diskstack/_vendor/greaseweazle/codec/macintosh/mac_gcr.py +238 -0
  22. diskstack/_vendor/greaseweazle/codec/micropolis/__init__.py +0 -0
  23. diskstack/_vendor/greaseweazle/codec/micropolis/micropolis.py +233 -0
  24. diskstack/_vendor/greaseweazle/codec/northstar/__init__.py +0 -0
  25. diskstack/_vendor/greaseweazle/codec/northstar/northstar.py +210 -0
  26. diskstack/_vendor/greaseweazle/data/__init__.py +0 -0
  27. diskstack/_vendor/greaseweazle/data/diskdefs.cfg +81 -0
  28. diskstack/_vendor/greaseweazle/data/diskdefs_acorn.cfg +119 -0
  29. diskstack/_vendor/greaseweazle/data/diskdefs_akai.cfg +27 -0
  30. diskstack/_vendor/greaseweazle/data/diskdefs_amiga.cfg +17 -0
  31. diskstack/_vendor/greaseweazle/data/diskdefs_apple2.cfg +31 -0
  32. diskstack/_vendor/greaseweazle/data/diskdefs_apricot.cfg +25 -0
  33. diskstack/_vendor/greaseweazle/data/diskdefs_atari.cfg +33 -0
  34. diskstack/_vendor/greaseweazle/data/diskdefs_atarist.cfg +76 -0
  35. diskstack/_vendor/greaseweazle/data/diskdefs_coco.cfg +98 -0
  36. diskstack/_vendor/greaseweazle/data/diskdefs_commodore.cfg +127 -0
  37. diskstack/_vendor/greaseweazle/data/diskdefs_datageneral.cfg +8 -0
  38. diskstack/_vendor/greaseweazle/data/diskdefs_dec.cfg +37 -0
  39. diskstack/_vendor/greaseweazle/data/diskdefs_dragon.cfg +61 -0
  40. diskstack/_vendor/greaseweazle/data/diskdefs_eagle.cfg +25 -0
  41. diskstack/_vendor/greaseweazle/data/diskdefs_ensoniq.cfg +39 -0
  42. diskstack/_vendor/greaseweazle/data/diskdefs_epson.cfg +95 -0
  43. diskstack/_vendor/greaseweazle/data/diskdefs_gem.cfg +15 -0
  44. diskstack/_vendor/greaseweazle/data/diskdefs_hp.cfg +25 -0
  45. diskstack/_vendor/greaseweazle/data/diskdefs_ibm.cfg +140 -0
  46. diskstack/_vendor/greaseweazle/data/diskdefs_kaypro.cfg +58 -0
  47. diskstack/_vendor/greaseweazle/data/diskdefs_luxor.cfg +96 -0
  48. diskstack/_vendor/greaseweazle/data/diskdefs_mac.cfg +71 -0
  49. diskstack/_vendor/greaseweazle/data/diskdefs_micropolis.cfg +73 -0
  50. diskstack/_vendor/greaseweazle/data/diskdefs_mm1.cfg +69 -0
  51. diskstack/_vendor/greaseweazle/data/diskdefs_msx.cfg +45 -0
  52. diskstack/_vendor/greaseweazle/data/diskdefs_northstar.cfg +37 -0
  53. diskstack/_vendor/greaseweazle/data/diskdefs_occ1.cfg +23 -0
  54. diskstack/_vendor/greaseweazle/data/diskdefs_olivetti.cfg +18 -0
  55. diskstack/_vendor/greaseweazle/data/diskdefs_pc98.cfg +69 -0
  56. diskstack/_vendor/greaseweazle/data/diskdefs_raw.cfg +25 -0
  57. diskstack/_vendor/greaseweazle/data/diskdefs_rm.cfg +98 -0
  58. diskstack/_vendor/greaseweazle/data/diskdefs_sci.cfg +15 -0
  59. diskstack/_vendor/greaseweazle/data/diskdefs_sega.cfg +13 -0
  60. diskstack/_vendor/greaseweazle/data/diskdefs_sharp.cfg +13 -0
  61. diskstack/_vendor/greaseweazle/data/diskdefs_thomson.cfg +78 -0
  62. diskstack/_vendor/greaseweazle/data/diskdefs_tsc.cfg +81 -0
  63. diskstack/_vendor/greaseweazle/data/diskdefs_xerox.cfg +59 -0
  64. diskstack/_vendor/greaseweazle/data/diskdefs_zx.cfg +245 -0
  65. diskstack/_vendor/greaseweazle/error.py +19 -0
  66. diskstack/_vendor/greaseweazle/flux.py +315 -0
  67. diskstack/_vendor/greaseweazle/image/__init__.py +0 -0
  68. diskstack/_vendor/greaseweazle/image/a2r.py +133 -0
  69. diskstack/_vendor/greaseweazle/image/acorn.py +28 -0
  70. diskstack/_vendor/greaseweazle/image/adf.py +15 -0
  71. diskstack/_vendor/greaseweazle/image/apple2.py +18 -0
  72. diskstack/_vendor/greaseweazle/image/apridisk.py +107 -0
  73. diskstack/_vendor/greaseweazle/image/caps.py +464 -0
  74. diskstack/_vendor/greaseweazle/image/d64.py +51 -0
  75. diskstack/_vendor/greaseweazle/image/d81.py +28 -0
  76. diskstack/_vendor/greaseweazle/image/d88.py +211 -0
  77. diskstack/_vendor/greaseweazle/image/dcp.py +45 -0
  78. diskstack/_vendor/greaseweazle/image/dim.py +53 -0
  79. diskstack/_vendor/greaseweazle/image/dmk.py +166 -0
  80. diskstack/_vendor/greaseweazle/image/dsk.py +31 -0
  81. diskstack/_vendor/greaseweazle/image/edsk.py +591 -0
  82. diskstack/_vendor/greaseweazle/image/fd.py +23 -0
  83. diskstack/_vendor/greaseweazle/image/fdi.py +41 -0
  84. diskstack/_vendor/greaseweazle/image/hdm.py +15 -0
  85. diskstack/_vendor/greaseweazle/image/hfe.py +742 -0
  86. diskstack/_vendor/greaseweazle/image/image.py +130 -0
  87. diskstack/_vendor/greaseweazle/image/imd.py +201 -0
  88. diskstack/_vendor/greaseweazle/image/img.py +111 -0
  89. diskstack/_vendor/greaseweazle/image/kryoflux.py +304 -0
  90. diskstack/_vendor/greaseweazle/image/mgt.py +15 -0
  91. diskstack/_vendor/greaseweazle/image/msa.py +153 -0
  92. diskstack/_vendor/greaseweazle/image/nfd.py +113 -0
  93. diskstack/_vendor/greaseweazle/image/nsi.py +47 -0
  94. diskstack/_vendor/greaseweazle/image/scp.py +471 -0
  95. diskstack/_vendor/greaseweazle/image/sf7.py +15 -0
  96. diskstack/_vendor/greaseweazle/image/sharp2d.py +15 -0
  97. diskstack/_vendor/greaseweazle/image/td0.py +141 -0
  98. diskstack/_vendor/greaseweazle/image/xdf.py +15 -0
  99. diskstack/_vendor/greaseweazle/optimised/__init__.py +22 -0
  100. diskstack/_vendor/greaseweazle/tools/__init__.py +0 -0
  101. diskstack/_vendor/greaseweazle/tools/util.py +133 -0
  102. diskstack/_vendor/greaseweazle/track.py +494 -0
  103. diskstack/cache.py +210 -0
  104. diskstack/candidates.py +396 -0
  105. diskstack/cli.py +348 -0
  106. diskstack/errors.py +5 -0
  107. diskstack/filler.py +29 -0
  108. diskstack/formats.py +381 -0
  109. diskstack/parallel.py +73 -0
  110. diskstack/report.py +353 -0
  111. diskstack/stack.py +317 -0
  112. diskstack-1.0.0.dist-info/METADATA +329 -0
  113. diskstack-1.0.0.dist-info/RECORD +119 -0
  114. diskstack-1.0.0.dist-info/WHEEL +5 -0
  115. diskstack-1.0.0.dist-info/entry_points.txt +2 -0
  116. diskstack-1.0.0.dist-info/licenses/LICENSE +21 -0
  117. diskstack-1.0.0.dist-info/licenses/NOTICE +55 -0
  118. diskstack-1.0.0.dist-info/licenses/diskstack/_vendor/greaseweazle/COPYING +24 -0
  119. 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:
@@ -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: