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,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
|