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,471 @@
1
+ # greaseweazle/image/scp.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 Dict, Tuple, Optional, List
9
+
10
+ import struct, time
11
+ from enum import IntFlag
12
+
13
+ from diskstack._vendor.greaseweazle import __version__
14
+ from diskstack._vendor.greaseweazle import error
15
+ from diskstack._vendor.greaseweazle.codec import codec
16
+ from diskstack._vendor.greaseweazle.flux import Flux, HasFlux
17
+ from diskstack._vendor.greaseweazle.tools import util
18
+ from diskstack._vendor.greaseweazle.track import MasterTrack
19
+ from .image import Image, ImageOpts
20
+
21
+ # SCP image specification can be found at Jim Drew's site:
22
+ # https://www.cbmstuff.com/downloads/scp/scp_image_specs.txt
23
+
24
+ # Names for disktype byte in SCP file header
25
+ DiskType = {
26
+ 'c64': 0x00,
27
+ 'amiga': 0x04,
28
+ 'amigahd': 0x08,
29
+ 'atari800-sd': 0x10,
30
+ 'atari800-dd': 0x11,
31
+ 'atari800-ed': 0x12,
32
+ 'atarist-ss': 0x14,
33
+ 'atarist-ds': 0x15,
34
+ 'appleii': 0x20,
35
+ 'appleiipro': 0x21,
36
+ 'apple-400k': 0x24,
37
+ 'apple-800k': 0x25,
38
+ 'apple-1m44': 0x26,
39
+ 'ibmpc-360k': 0x30,
40
+ 'ibmpc-720k': 0x31,
41
+ 'ibmpc-1m2': 0x32,
42
+ 'ibmpc-1m44': 0x33,
43
+ 'trs80_sssd': 0x40,
44
+ 'trs80_ssdd': 0x41,
45
+ 'trs80_dssd': 0x42,
46
+ 'trs80_dsdd': 0x43,
47
+ 'ti-99/4a': 0x50,
48
+ 'roland-d20': 0x60,
49
+ 'amstrad-cpc': 0x70,
50
+ 'other-320k': 0x80,
51
+ 'other-1m2': 0x81,
52
+ 'other-720k': 0x84,
53
+ 'other-1m44': 0x85,
54
+ 'tape-gcr1': 0xe0,
55
+ 'tape-gcr2': 0xe1,
56
+ 'tape-mfm': 0xe2,
57
+ 'hdd-mfm': 0xf0,
58
+ 'hdd-rll': 0xf1
59
+ }
60
+
61
+
62
+ class SCPHeaderFlags(IntFlag):
63
+ INDEXED = 1<<0 # image used the index mark to cue tracks
64
+ TPI_96 = 1<<1 # drive is 96 TPI, otherwise 48 TPI
65
+ RPM_360 = 1<<2 # drive is 360RPM, otherwise 300RPM
66
+ NORMALISED = 1<<3 # flux has been normalized, otherwise is raw
67
+ READWRITE = 1<<4 # image is read/write capable, otherwise read-only
68
+ FOOTER = 1<<5 # image contains an extension footer
69
+ EXTENDED_MODE = 1<<6 # image is the extended type for other media
70
+ FLUX_CREATOR = 1<<7 # image was created by a non SuperCard Pro Device
71
+
72
+
73
+ class SCPOpts(ImageOpts):
74
+ """legacy_ss: Set to True to generate (incorrect) legacy single-sided
75
+ SCP image.
76
+ revs: Number of revolutions to output per track.
77
+ """
78
+
79
+ w_settings = [ 'disktype', 'legacy_ss', 'revs' ]
80
+
81
+ def __init__(self) -> None:
82
+ self.legacy_ss = False
83
+ self._disktype = 0x80 # Other
84
+ self._revs: Optional[int] = None
85
+
86
+ @property
87
+ def disktype(self) -> int:
88
+ return self._disktype
89
+ @disktype.setter
90
+ def disktype(self, disktype):
91
+ try:
92
+ self._disktype = DiskType[disktype.lower()]
93
+ except KeyError:
94
+ try:
95
+ self._disktype = int(disktype, 0)
96
+ except ValueError:
97
+ l = [ x.lower() for x in DiskType.keys() ]
98
+ l.sort()
99
+ raise error.Fatal("Bad SCP disktype: '%s'\n" % disktype
100
+ + 'Valid types:\n' + util.columnify(l))
101
+
102
+ @property
103
+ def revs(self) -> Optional[int]:
104
+ return self._revs
105
+ @revs.setter
106
+ def revs(self, revs: str) -> None:
107
+ try:
108
+ self._revs = int(revs)
109
+ if self._revs < 1:
110
+ raise ValueError
111
+ except ValueError:
112
+ raise error.Fatal("Kryoflux: Invalid revs: '%s'" % revs)
113
+
114
+
115
+ class SCPTrack:
116
+
117
+ def __init__(self, tdh, dat, splice=None):
118
+ self.tdh = tdh
119
+ self.dat = dat
120
+ self.splice = splice
121
+
122
+
123
+ class SCP(Image):
124
+
125
+ # 40MHz
126
+ sample_freq = 40000000
127
+ opts: SCPOpts
128
+
129
+
130
+ def __init__(self, name: str, _fmt) -> None:
131
+ self.opts = SCPOpts()
132
+ self.nr_revs: Optional[int] = None
133
+ self.to_track: Dict[int, SCPTrack] = dict()
134
+ self.index_cued = True
135
+ self.filename = name
136
+
137
+
138
+ def side_count(self) -> List[int]:
139
+ s = [0,0] # non-empty tracks on each side
140
+ for tnr in self.to_track:
141
+ s[tnr&1] += 1
142
+ return s
143
+
144
+
145
+ def from_bytes(self, dat: bytes) -> None:
146
+
147
+ splices = None
148
+
149
+ (sig, _, disk_type, nr_revs, _, _, flags, _, single_sided, _,
150
+ checksum) = struct.unpack("<3s9BI", dat[0:16])
151
+ error.check(sig == b"SCP", "SCP: Bad signature")
152
+
153
+ if sum(dat[16:]) & 0xffffffff != checksum:
154
+ print('SCP: WARNING: Bad image checksum')
155
+
156
+ index_cued = (flags & 1) == 1 or nr_revs == 1
157
+
158
+ # Some tools generate a short TLUT. We handle this by truncating the
159
+ # TLUT at the first Track Data Header.
160
+ trk_offs = struct.unpack("<168I", dat[16:0x2b0])
161
+ for i in range(168):
162
+ try:
163
+ off = trk_offs[i]
164
+ except IndexError:
165
+ break
166
+ if off == 0 or off >= 0x2b0:
167
+ continue
168
+ off = off//4 - 4
169
+ error.check(off >= 0, "SCP: Bad Track Table")
170
+ trk_offs = trk_offs[:off]
171
+
172
+ # Parse the extension block introduced by github:markusC64/g64conv.
173
+ # b'EXTS', length, <length bytes: Extension Area>
174
+ # Extension Area contains consecutive chunks of the form:
175
+ # ID, length, <length bytes: ID-specific data>
176
+ ext_sig, ext_len = None, 0
177
+ if len(dat) >= 0x2b8:
178
+ ext_sig, ext_len = struct.unpack('<4sI', dat[0x2b0:0x2b8])
179
+ min_tdh = min(filter(lambda x: x != 0, trk_offs), default=0)
180
+ if ext_sig == b'EXTS' and 0x2b8 + ext_len <= min_tdh:
181
+ pos, end = 0x2b8, 0x2b8 + ext_len
182
+ while end - pos >= 8:
183
+ chk_sig, chk_len = struct.unpack('<4sI', dat[pos:pos+8])
184
+ pos += 8
185
+ # WRSP: WRite SPlice information block.
186
+ # Data is comprised of >= 169 32-bit values:
187
+ # 0: Flags (currently unused; must be zero)
188
+ # N: Write splice/overlap position for track N, in SCP ticks
189
+ # (zero if the track is unused)
190
+ if chk_sig == b'WRSP' and chk_len >= 169*4:
191
+ # Write-splice positions for writing out SCP tracks
192
+ # correctly to disk.
193
+ splices = struct.unpack('<168I', dat[pos+4:pos+169*4])
194
+ pos += chk_len
195
+
196
+ for trknr in range(len(trk_offs)):
197
+
198
+ trk_off = trk_offs[trknr]
199
+ if trk_off == 0:
200
+ continue
201
+
202
+ # Parse the SCP track header and extract the flux data.
203
+ thdr = dat[trk_off:trk_off+4+12*nr_revs]
204
+ sig, tnr = struct.unpack("<3sB", thdr[:4])
205
+ error.check(sig == b"TRK", "SCP: Missing track signature")
206
+ error.check(tnr == trknr, "SCP: Wrong track number in header")
207
+ thdr = thdr[4:] # Remove TRK header
208
+
209
+ # Strip empty trailing revolutions (old versions of FluxEngine).
210
+ while thdr:
211
+ e_ticks, e_nr, e_off = struct.unpack("<3I", thdr[-12:])
212
+ if e_nr != 0 and e_ticks != 0:
213
+ break
214
+ thdr = thdr[:-12]
215
+ # Bail if all revolutions are empty.
216
+ if not thdr:
217
+ continue
218
+
219
+ # Clip the first revolution if it's not flagged as index cued
220
+ # and there's more than one revolution.
221
+ if not index_cued and len(thdr) > 12:
222
+ thdr = thdr[12:]
223
+ s_off, = struct.unpack("<I", thdr[8:12])
224
+
225
+ e_off += e_nr*2
226
+ if s_off == e_off:
227
+ # FluxEngine creates dummy TDHs for empty tracks.
228
+ # Bail on them here.
229
+ continue
230
+
231
+ tdat = dat[trk_off+s_off:trk_off+e_off]
232
+ track = SCPTrack(thdr, tdat)
233
+ if splices is not None:
234
+ track.splice = splices[trknr]
235
+ self.to_track[trknr] = track
236
+
237
+ s = self.side_count()
238
+
239
+ # C64 images with halftracks are genberated by Supercard Pro using
240
+ # consecutive track numbers. That needs fixup here for our layout.
241
+ # We re-use the legacy-single-sided fixup below.
242
+ if (single_sided == 0 and disk_type == 0
243
+ and s[1] and s[0]==s[1]+1 and s[0] < 42):
244
+ single_sided = 1
245
+ print('SCP: Importing C64 image with halftracks')
246
+
247
+ # Some tools produce (or used to produce) single-sided images using
248
+ # consecutive entries in the TLUT. This needs fixing up.
249
+ if single_sided and s[0] and s[1]:
250
+ new_dict = dict()
251
+ for tnr in self.to_track:
252
+ new_dict[tnr*2+single_sided-1] = self.to_track[tnr]
253
+ self.to_track = new_dict
254
+ print('SCP: Imported legacy single-sided image')
255
+
256
+
257
+ def get_track(self, cyl: int, side: int) -> Optional[Flux]:
258
+ tracknr = cyl * 2 + side
259
+ if not tracknr in self.to_track:
260
+ return None
261
+ track = self.to_track[tracknr]
262
+ tdh, dat = track.tdh, track.dat
263
+
264
+ index_list = []
265
+ while tdh:
266
+ ticks, _, _ = struct.unpack("<3I", tdh[:12])
267
+ index_list.append(ticks)
268
+ tdh = tdh[12:]
269
+
270
+ # Decode the SCP flux data into a simple list of flux times.
271
+ flux_list = []
272
+ val = 0
273
+ for i in range(0, len(dat), 2):
274
+ x = dat[i]*256 + dat[i+1]
275
+ if x == 0:
276
+ val += 65536
277
+ continue
278
+ flux_list.append(val + x)
279
+ val = 0
280
+
281
+ flux = Flux(index_list, flux_list, SCP.sample_freq)
282
+ flux.splice = track.splice
283
+ return flux
284
+
285
+
286
+ def emit_track(self, cyl: int, side: int, track: HasFlux) -> None:
287
+ """Converts @track into a Supercard Pro Track and appends it to
288
+ the current image-in-progress.
289
+ """
290
+
291
+ if isinstance(track, codec.Codec):
292
+ track = track.master_track()
293
+ if isinstance(track, MasterTrack):
294
+ # Get a consistent number of revolutions, allowing for data
295
+ # across the index mark (which ideally warrants two revolutions).
296
+ mt_revs = 2 if self.opts.revs is None else self.opts.revs
297
+ flux = track.flux(revs = mt_revs)
298
+ else:
299
+ flux = track.flux()
300
+
301
+ # External tools and emulators generally seem to work best (or only)
302
+ # with index-cued SCP image files. So let's make sure we give them
303
+ # what they want.
304
+ flux.cue_at_index()
305
+
306
+ if self.opts.revs is not None:
307
+ flux.set_nr_revs(self.opts.revs)
308
+
309
+ if not flux.index_cued:
310
+ self.index_cued = False
311
+
312
+ nr_revs = len(flux.index_list)
313
+ if not self.nr_revs:
314
+ self.nr_revs = nr_revs
315
+ else:
316
+ self.nr_revs = min(self.nr_revs, nr_revs)
317
+
318
+ factor = SCP.sample_freq / flux.sample_freq
319
+ splice = None if flux.splice is None else round(flux.splice * factor)
320
+
321
+ tdh, dat = bytearray(), bytearray()
322
+ len_at_index = rev = 0
323
+ to_index = flux.index_list[0]
324
+ rem = 0.0
325
+
326
+ for x in flux.list:
327
+
328
+ # Does the next flux interval cross the index mark?
329
+ while to_index < x:
330
+ # Append to the TDH for the previous full revolution
331
+ tdh += struct.pack("<III",
332
+ round(flux.index_list[rev]*factor),
333
+ (len(dat) - len_at_index) // 2,
334
+ 4 + nr_revs*12 + len_at_index)
335
+ # Set up for the next revolution
336
+ len_at_index = len(dat)
337
+ rev += 1
338
+ if rev >= nr_revs:
339
+ # We're done: We simply discard any surplus flux samples
340
+ self.to_track[cyl*2+side] = SCPTrack(tdh, dat, splice)
341
+ return
342
+ to_index += flux.index_list[rev]
343
+
344
+ # Process the current flux sample into SCP "bitcell" format
345
+ to_index -= x
346
+ y = x * factor + rem
347
+ val = round(y)
348
+ if val <= 0:
349
+ continue
350
+ if (val & 65535) == 0:
351
+ val += 1
352
+ rem = y - val
353
+ while val >= 65536:
354
+ dat.append(0)
355
+ dat.append(0)
356
+ val -= 65536
357
+ dat.append(val>>8)
358
+ dat.append(val&255)
359
+
360
+ # Header for last track(s) in case we ran out of flux timings.
361
+ while rev < nr_revs:
362
+ tdh += struct.pack("<III",
363
+ round(flux.index_list[rev]*factor),
364
+ (len(dat) - len_at_index) // 2,
365
+ 4 + nr_revs*12 + len_at_index)
366
+ len_at_index = len(dat)
367
+ rev += 1
368
+
369
+ self.to_track[cyl*2+side] = SCPTrack(tdh, dat, splice)
370
+
371
+
372
+ def get_image(self) -> bytes:
373
+
374
+ # Work out the single-sided byte code
375
+ s = self.side_count()
376
+ if s[0] and s[1]:
377
+ single_sided = 0
378
+ elif s[0]:
379
+ single_sided = 1
380
+ else:
381
+ single_sided = 2
382
+
383
+ to_track = self.to_track
384
+ if single_sided and self.opts.legacy_ss:
385
+ print('SCP: Generated legacy single-sided image')
386
+ to_track = dict()
387
+ for tnr in self.to_track:
388
+ to_track[tnr//2] = self.to_track[tnr]
389
+
390
+ ntracks = max(to_track, default=0) + 1
391
+
392
+ # Emit a WRSP block iff we have at least one known splice point.
393
+ emit_wrsp = False
394
+ for track in to_track.values():
395
+ if track.splice is not None:
396
+ emit_wrsp = True
397
+ wrsp, wrsp_len = bytearray(), 0
398
+ if emit_wrsp:
399
+ wrsp_len = (2+2+169)*4
400
+ wrsp += struct.pack('<4sI4s2I',
401
+ b'EXTS', wrsp_len- 8, # EXTS header
402
+ b'WRSP', wrsp_len-16, # WRSP header
403
+ 0) # WRSP flags field
404
+
405
+ # Generate the TLUT and concatenate all the tracks together.
406
+ trk_offs, trk_offs_len = bytearray(), 0x2a0
407
+ trk_dat = bytearray()
408
+ trk_start = 0x10 + trk_offs_len + wrsp_len
409
+ for tnr in range(ntracks):
410
+ if tnr in to_track:
411
+ track = to_track[tnr]
412
+ trk_offs += struct.pack("<I", trk_start + len(trk_dat))
413
+ trk_dat += struct.pack("<3sB", b"TRK", tnr)
414
+ trk_dat += track.tdh + track.dat
415
+ splice = 0 if track.splice is None else track.splice
416
+ else:
417
+ trk_offs += struct.pack("<I", 0)
418
+ splice = 0
419
+ if emit_wrsp:
420
+ wrsp += struct.pack("<I", splice)
421
+ error.check(len(trk_offs) <= trk_offs_len, "SCP: Too many tracks")
422
+ trk_offs += bytes(trk_offs_len - len(trk_offs))
423
+ wrsp += bytes(wrsp_len - len(wrsp))
424
+
425
+ creation_time = round(time.time())
426
+ footer_offs = trk_start + len(trk_dat)
427
+ app_name = f'Greaseweazle {__version__}'.encode()
428
+ footer = struct.pack('<H', len(app_name)) + app_name + b'\0'
429
+ footer += struct.pack('<6I2Q4B4s',
430
+ 0, # drive manufacturer
431
+ 0, # drive model
432
+ 0, # drive serial
433
+ 0, # creator name
434
+ footer_offs, # application name
435
+ 0, # comments
436
+ creation_time, # creation time
437
+ creation_time, # modification time
438
+ 0, # application version
439
+ 0, # hardware version
440
+ 0, # firmware version
441
+ 0x24, # format version (v2.4)
442
+ b'FPCS')
443
+
444
+ # Concatenate all data together for checksumming.
445
+ data = trk_offs + wrsp + trk_dat + footer
446
+
447
+ # Generate the image header.
448
+ flags = SCPHeaderFlags.TPI_96 | SCPHeaderFlags.FOOTER
449
+ if self.index_cued:
450
+ flags |= SCPHeaderFlags.INDEXED
451
+ nr_revs = self.nr_revs if self.nr_revs is not None else 0
452
+ header = struct.pack("<3s9BI",
453
+ b"SCP", # Signature
454
+ 0,
455
+ self.opts.disktype,
456
+ nr_revs,
457
+ 0, # start track
458
+ ntracks-1, # end track
459
+ flags,
460
+ 0, # 16-bit cell width
461
+ single_sided,
462
+ 0, # 25ns capture
463
+ sum(data) & 0xffffffff)
464
+
465
+ # Concatenate it all together and send it back.
466
+ return header + data
467
+
468
+
469
+ # Local variables:
470
+ # python-indent: 4
471
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/sf7.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 diskstack._vendor.greaseweazle.image.img import IMG
9
+
10
+ class SF7(IMG):
11
+ default_format = 'sega.sf7000'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/sharp2d.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 diskstack._vendor.greaseweazle.image.img import IMG
9
+
10
+ class SHARP2D(IMG):
11
+ default_format = 'sharp.2d'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End:
@@ -0,0 +1,141 @@
1
+ # greaseweazle/image/td0.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 Dict, Tuple, Optional
9
+
10
+ import struct
11
+ import crcmod.predefined
12
+
13
+ from diskstack._vendor.greaseweazle import __version__
14
+ from diskstack._vendor.greaseweazle import error
15
+ from diskstack._vendor.greaseweazle import optimised
16
+ from diskstack._vendor.greaseweazle.codec.ibm import ibm
17
+ from .image import Image
18
+
19
+ crc16 = crcmod.predefined.Crc('crc-16-teledisk')
20
+
21
+ class TD0(Image):
22
+
23
+ read_only = True
24
+
25
+ def __init__(self, name: str, _fmt) -> None:
26
+ self.to_track: Dict[Tuple[int,int],ibm.IBMTrack_Fixed] = dict()
27
+ self.filename = name
28
+
29
+
30
+ def from_bytes(self, dat: bytes) -> None:
31
+
32
+ # Check and strip the header
33
+ sig, td_ver, data_rate, stepping, n_sides, crc = struct.unpack(
34
+ '<2s2x2BxBxBH', dat[:12])
35
+ error.check(sig == b'TD' or sig == b'td', 'TD0: bad file signature')
36
+ error.check(crc16.new(dat[:10]).crcValue == crc,
37
+ 'TD0: bad file header crc')
38
+ print('TD0: Teledisk version %d.%d' % (td_ver>>4, td_ver&15))
39
+
40
+ if sig == b'td':
41
+ print('TD0: Advanced compression')
42
+ error.check(optimised.enabled,
43
+ 'TD0: Decompression requires optimised C extension')
44
+ dat = dat[:12] + optimised.td0_unpack(dat[12:])
45
+
46
+ # data_rate[7] = global FM flag
47
+ global_is_fm = (data_rate >> 7) == 1
48
+ data_rate &= 127
49
+
50
+ error.check(data_rate <= 2, 'TD0: bad data rate %d' % data_rate)
51
+ data_rate = [250, 300, 500][data_rate]
52
+ n_sides = 1 if n_sides == 1 else 2
53
+
54
+ off = 12
55
+
56
+ if stepping & 128:
57
+ crc, dlen, yr, mo, day, hr, minute, sec = struct.unpack(
58
+ '<2H6B', dat[off:off+10])
59
+ error.check(crc16.new(dat[off+2:off+10+dlen]).crcValue == crc,
60
+ 'TD0: bad comment header crc')
61
+ print('TD0: Created %d/%d/%d %d:%d:%d' %
62
+ (day, mo+1, yr+1900, hr, minute, sec))
63
+ off += 10 + dlen
64
+
65
+ while dat[off] != 255:
66
+
67
+ n_sec, cyl, head, crc = struct.unpack('4B', dat[off:off+4])
68
+ error.check(crc16.new(dat[off:off+3]).crcValue & 0xff == crc,
69
+ 'TD0: bad track header crc')
70
+ off += 4
71
+
72
+ track_is_fm = (head & 128) == 128 or global_is_fm
73
+ head &= 127
74
+
75
+ fmt = ibm.IBMTrack_FixedDef(['ibm.mfm','ibm.fm'][track_is_fm])
76
+ fmt.rpm, fmt.rate = 300, data_rate
77
+ if track_is_fm:
78
+ fmt.rate = fmt.rate // 2
79
+ fmt.secs = n_sec
80
+
81
+ secs = list()
82
+ for _ in range(n_sec):
83
+ id_c,id_h,id_r,id_n,flags,crc = struct.unpack(
84
+ '6B', dat[off:off+6])
85
+ fmt.sz.append(id_n)
86
+ if not (flags & 0x30):
87
+ dlen, enc = struct.unpack('<HB', dat[off+6:off+9])
88
+ dlen -= 1
89
+ blk = dat[off+9:off+9+dlen]
90
+ off += 9 + dlen
91
+ if enc == 1:
92
+ o, _blk = 0, bytearray()
93
+ while o < dlen:
94
+ c, = struct.unpack('<H', blk[o:o+2])
95
+ _blk += blk[o+2:o+4] * c
96
+ o += 4
97
+ blk = _blk
98
+ if enc == 2:
99
+ o, _blk = 0, bytearray()
100
+ while o < dlen:
101
+ c, n = blk[o], blk[o+1]
102
+ o += 2
103
+ if c == 0:
104
+ _blk += blk[o:o+n]
105
+ o += n
106
+ else:
107
+ _blk += blk[o:o+c*2] * n
108
+ o += c*2
109
+ blk = _blk
110
+ assert len(blk) == ibm.sec_sz(id_n)
111
+ error.check(crc16.new(blk).crcValue & 0xff == crc,
112
+ 'TD0: bad sector data crc')
113
+ else:
114
+ off += 6
115
+ blk = bytes(ibm.sec_sz(id_n))
116
+ secs.append((id_c,id_h,id_r,id_n,flags,blk))
117
+
118
+
119
+ fmt.finalise()
120
+ t = fmt.mk_track(cyl, head)
121
+
122
+ for nr, s in enumerate(t.sectors):
123
+ id_c,id_h,id_r,id_n,flags,blk = secs[nr]
124
+ s.crc = s.idam.crc = s.dam.crc = 0
125
+ s.idam.c, s.idam.h, s.idam.r = id_c, id_h, id_r
126
+ s.dam.data = blk
127
+ if flags & 4:
128
+ s.dam.mark = ibm.Mark.DDAM
129
+
130
+ self.to_track[cyl, head] = t
131
+
132
+
133
+ def get_track(self, cyl: int, side: int) -> Optional[ibm.IBMTrack_Fixed]:
134
+ if (cyl,side) not in self.to_track:
135
+ return None
136
+ return self.to_track[cyl,side]
137
+
138
+
139
+ # Local variables:
140
+ # python-indent: 4
141
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/xdf.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 diskstack._vendor.greaseweazle.image.img import IMG
9
+
10
+ class XDF(IMG):
11
+ default_format = 'pc98.2hd'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End:
@@ -0,0 +1,22 @@
1
+ # greaseweazle/optimised/__init__.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
+ import os
9
+
10
+ gw_opt = os.environ.get('GW_OPT')
11
+ enabled = gw_opt is None or gw_opt.lower().startswith('y')
12
+ if enabled:
13
+ try:
14
+ from .optimised import *
15
+ except ModuleNotFoundError:
16
+ enabled = False
17
+ else:
18
+ pass
19
+
20
+ # Local variables:
21
+ # python-indent: 4
22
+ # End:
File without changes