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,591 @@
1
+ # greaseweazle/image/edsk.py
2
+ #
3
+ # Some of the code here is heavily inspired by Simon Owen's SAMdisk:
4
+ # https://simonowen.com/samdisk/
5
+ #
6
+ # Written & released by Keir Fraser <keir.xen@gmail.com>
7
+ #
8
+ # This is free and unencumbered software released into the public domain.
9
+ # See the file COPYING for more details, or visit <http://unlicense.org>.
10
+
11
+ from typing import Dict, Tuple, Optional, List, Union
12
+
13
+ import binascii, math, struct
14
+ import itertools as it
15
+ from bitarray import bitarray
16
+
17
+ from diskstack._vendor.greaseweazle import __version__
18
+ from diskstack._vendor.greaseweazle import error
19
+ from diskstack._vendor.greaseweazle.flux import Flux
20
+ from diskstack._vendor.greaseweazle.codec.ibm import ibm
21
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLLTrack
22
+ from .image import Image
23
+
24
+ class EDSKRate:
25
+ Unknown = 0
26
+ DoubleDensity = 1
27
+ HighDensity = 2
28
+ ExtendedDensity = 3
29
+
30
+ class SR1:
31
+ SUCCESS = 0x00
32
+ CANNOT_FIND_ID_ADDRESS = 0x01
33
+ WRITE_PROTECT_DETECTED = 0x02
34
+ CANNOT_FIND_SECTOR_ID = 0x04
35
+ RESERVED1 = 0x08
36
+ OVERRUN = 0x10
37
+ CRC_ERROR = 0x20
38
+ RESERVED2 = 0x40
39
+ END_OF_CYLINDER = 0x80
40
+
41
+ class SR2:
42
+ SUCCESS = 0x00
43
+ MISSING_ADDRESS_MARK = 0x01
44
+ BAD_CYLINDER = 0x02
45
+ SCAN_COMMAND_FAILED = 0x04
46
+ SCAN_COMMAND_EQUAL = 0x08
47
+ WRONG_CYLINDER_DETECTED = 0x10
48
+ CRC_ERROR_IN_SECTOR_DATA = 0x20
49
+ SECTOR_WITH_DELETED_DATA = 0x40
50
+ RESERVED = 0x80
51
+
52
+ class SectorErrors:
53
+ def __init__(self, sr1: int, sr2: int):
54
+ self.id_crc_error = (sr1 & SR1.CRC_ERROR) != 0
55
+ self.data_not_found = (sr2 & SR2.MISSING_ADDRESS_MARK) != 0
56
+ self.data_crc_error = (sr2 & SR2.CRC_ERROR_IN_SECTOR_DATA) != 0
57
+ self.deleted_dam = (sr2 & SR2.SECTOR_WITH_DELETED_DATA) != 0
58
+ if self.data_crc_error:
59
+ # uPD765 sets both id and data flags for data CRC errors
60
+ self.id_crc_error = False
61
+ if (# normal data
62
+ (sr1 == SR1.SUCCESS and sr2 == SR2.SUCCESS) or
63
+ # deleted data
64
+ (sr1 == SR1.SUCCESS and sr2 == SR2.SECTOR_WITH_DELETED_DATA) or
65
+ # end of track
66
+ (sr1 == SR1.END_OF_CYLINDER and sr2 == SR2.SUCCESS) or
67
+ # id crc error
68
+ (sr1 == SR1.CRC_ERROR and sr2 == SR2.SUCCESS) or
69
+ # normal data crc error
70
+ (sr1 == SR1.CRC_ERROR and sr2 == SR2.CRC_ERROR_IN_SECTOR_DATA) or
71
+ # deleted data crc error
72
+ (sr1 == SR1.CRC_ERROR and sr2 == (SR2.CRC_ERROR_IN_SECTOR_DATA |
73
+ SR2.SECTOR_WITH_DELETED_DATA)) or
74
+ # data field missing (some FDCs set AM in ST1)
75
+ (sr1 == SR1.CANNOT_FIND_ID_ADDRESS
76
+ and sr2 == SR2.MISSING_ADDRESS_MARK) or
77
+ # data field missing (some FDCs don't)
78
+ (sr1 == SR1.SUCCESS and sr2 == SR2.MISSING_ADDRESS_MARK) or
79
+ # CHRN mismatch
80
+ (sr1 == SR1.CANNOT_FIND_SECTOR_ID and sr2 == SR2.SUCCESS) or
81
+ # CHRN mismatch, including wrong cylinder
82
+ (sr1 == SR1.CANNOT_FIND_SECTOR_ID
83
+ and sr2 == SR2.WRONG_CYLINDER_DETECTED)):
84
+ pass
85
+ else:
86
+ print('Unusual status flags (ST1=%02X ST2=%02X)' % (sr1, sr2))
87
+
88
+ class EDSKTrack:
89
+
90
+ gap_presync = 12
91
+ gap_4a = 80 # Post-Index
92
+ gap_1 = 50 # Post-IAM
93
+ gap_2 = 22 # Post-IDAM
94
+
95
+ gapbyte = 0x4e
96
+
97
+ verify_len: int
98
+ verify_revs: float = 1
99
+
100
+ def __init__(self, rate = 0) -> None:
101
+ self.time_per_rev = 0.2
102
+ if rate == EDSKRate.HighDensity:
103
+ self.clock = 1e-6
104
+ elif rate == EDSKRate.ExtendedDensity:
105
+ self.clock = 5e-7
106
+ else: # Double Density, or unknown
107
+ self.clock = 2e-6
108
+ self.bits = bitarray(endian='big')
109
+ self.weak: List[Tuple[int,int]] = []
110
+ self.bytes = bytearray()
111
+
112
+ def master_track(self) -> MasterTrack:
113
+ track = MasterTrack(
114
+ bits = self.bits,
115
+ time_per_rev = self.time_per_rev,
116
+ weak = self.weak)
117
+ track.verify = self
118
+ return track
119
+
120
+ def _find_sync(self, bits: bitarray, sync, start) -> Optional[int]:
121
+ for offs in bits.search(sync):
122
+ if offs >= start:
123
+ return offs
124
+ return None
125
+
126
+ def verify_track(self, flux: Flux) -> bool:
127
+ flux.cue_at_index()
128
+ raw = PLLTrack(time_per_rev = self.time_per_rev,
129
+ clock = self.time_per_rev / len(self.bits),
130
+ data = flux)
131
+ bits, _ = raw.get_all_data()
132
+ weak_iter = it.chain(self.weak, [(self.verify_len+1,1)])
133
+ weak = next(weak_iter)
134
+
135
+ # Start checking from the IAM sync
136
+ dump_start = self._find_sync(bits, ibm.mfm_iam_sync, 0)
137
+ self_start = self._find_sync(self.bits, ibm.mfm_iam_sync, 0)
138
+ assert self_start is not None
139
+
140
+ # Include the IAM pre-sync header
141
+ if dump_start is None:
142
+ return False
143
+ dump_start -= self.gap_presync * 16
144
+ self_start -= self.gap_presync * 16
145
+
146
+ while self_start is not None and dump_start is not None:
147
+
148
+ # Find the weak areas immediately before and after the current
149
+ # region to be checked.
150
+ s,n = None,None
151
+ while self_start > weak[0]:
152
+ s,n = weak
153
+ weak = next(weak_iter)
154
+
155
+ # If there is a weak area preceding us, move the start point to
156
+ # immediately follow the weak area.
157
+ if s is not None:
158
+ assert n is not None # mypy
159
+ delta = self_start - (s + n + 16)
160
+ self_start -= delta
161
+ dump_start -= delta
162
+
163
+ # Truncate the region at the next weak area, or the last sector.
164
+ self_end = max(self_start, min(weak[0], self.verify_len+1))
165
+ dump_end = dump_start + self_end - self_start
166
+
167
+ # Extract the corresponding areas from the pristine track and
168
+ # from the dump, and check that they match.
169
+ if bits[dump_start:dump_end] != self.bits[self_start:self_end]:
170
+ return False
171
+
172
+ # Find the next A1A1A1 sync pattern
173
+ dump_start = self._find_sync(bits, ibm.mfm_sync, dump_end)
174
+ self_start = self._find_sync(self.bits, ibm.mfm_sync, self_end)
175
+
176
+ # Did we verify all regions in the pristine track?
177
+ return self_start is None
178
+
179
+ class EDSK(Image):
180
+
181
+ def __init__(self, name: str, _fmt) -> None:
182
+ self.to_track: Dict[Tuple[int,int],
183
+ Union[ibm.IBMTrack,EDSKTrack]] = dict()
184
+ self.filename = name
185
+
186
+ # Find all weak ranges in the given sector data copies.
187
+ @staticmethod
188
+ def find_weak_ranges(dat: bytes, size: int) -> List[Tuple[int,int]]:
189
+ orig = dat[:size]
190
+ s, w = size, []
191
+ # Find first mismatching byte across all copies
192
+ for i in range(1, len(dat)//size):
193
+ diff = [x^y for x, y in zip(orig, dat[size*i:size*(i+1)])]
194
+ weak = [idx for idx, val in enumerate(diff) if val != 0]
195
+ if weak:
196
+ s = min(s, weak[0])
197
+ # Look for runs of filler
198
+ i = s
199
+ while i < size:
200
+ j, x = i, orig[i]
201
+ while j < size and orig[j] == x:
202
+ j += 1
203
+ if j-i >= 16:
204
+ w.append((s,i-s))
205
+ s = j
206
+ i = j
207
+ # Append final weak area if any.
208
+ if s < size:
209
+ w.append((s,size-s))
210
+ return w
211
+
212
+ @staticmethod
213
+ def _build_8k_track(sectors: List) -> Optional[EDSKTrack]:
214
+ if len(sectors) != 1:
215
+ return None
216
+ c,h,r,n,errs,data = sectors[0]
217
+ if n != 6:
218
+ return None
219
+ if errs.id_crc_error or errs.data_not_found or not errs.data_crc_error:
220
+ return None
221
+ # Magic longtrack value is for Coin-Op Hits. Taken from SAMdisk.
222
+ if len(data) > 6307:
223
+ data = data[:6307]
224
+ track = EDSKTrack()
225
+ t = track.bytes
226
+ # Post-index gap
227
+ t += ibm.encode(bytes([track.gapbyte] * 16))
228
+ # IAM
229
+ t += ibm.encode(bytes(track.gap_presync))
230
+ t += ibm.mfm_iam_sync_bytes
231
+ t += ibm.encode(bytes([ibm.Mark.IAM]))
232
+ t += ibm.encode(bytes([track.gapbyte] * 16))
233
+ # IDAM
234
+ t += ibm.encode(bytes(track.gap_presync))
235
+ t += ibm.mfm_sync_bytes
236
+ am = bytes([0xa1, 0xa1, 0xa1, ibm.Mark.IDAM, c, h, r, n])
237
+ crc = ibm.crc16.new(am).crcValue
238
+ am += struct.pack('>H', crc)
239
+ t += ibm.encode(am[3:])
240
+ t += ibm.encode(bytes([track.gapbyte] * track.gap_2))
241
+ # DAM
242
+ t += ibm.encode(bytes(track.gap_presync))
243
+ t += ibm.mfm_sync_bytes
244
+ dmark = (ibm.Mark.DDAM if errs.deleted_dam
245
+ else ibm.Mark.DAM)
246
+ am = bytes([0xa1, 0xa1, 0xa1, dmark]) + data
247
+ t += ibm.encode(am[3:])
248
+ return track
249
+
250
+ @staticmethod
251
+ def _build_kbi19_track(sectors: List) -> Optional[EDSKTrack]:
252
+ ids = [0,1,4,7,10,13,16,2,5,8,11,14,17,3,6,9,12,15,18]
253
+ if len(sectors) != len(ids):
254
+ return None
255
+ for s,id in zip(sectors,ids):
256
+ c,h,r,n,_,_ = s
257
+ if r != id or n != 2:
258
+ return None
259
+ def addcrc(t,n):
260
+ crc = ibm.crc16.new(ibm.decode(t[-n*2:])).crcValue
261
+ t += ibm.encode(struct.pack('>H', crc))
262
+ track = EDSKTrack()
263
+ t = track.bytes
264
+ # Post-index gap
265
+ t += ibm.encode(bytes([track.gapbyte] * 64))
266
+ # IAM
267
+ t += ibm.encode(bytes(track.gap_presync))
268
+ t += ibm.mfm_iam_sync_bytes
269
+ t += ibm.encode(bytes([ibm.Mark.IAM]))
270
+ t += ibm.encode(bytes([track.gapbyte] * 50))
271
+ for idx, s in enumerate(sectors):
272
+ c,h,r,n,errs,data = s
273
+ # IDAM
274
+ t += ibm.encode(bytes(track.gap_presync))
275
+ t += ibm.mfm_sync_bytes
276
+ t += ibm.encode(bytes([ibm.Mark.IDAM, c, h, r, n]))
277
+ addcrc(t, 8)
278
+ if r == 0:
279
+ t += ibm.encode(bytes([track.gapbyte] * 17))
280
+ t += ibm.encode(b' KBI ')
281
+ else:
282
+ t += ibm.encode(bytes([track.gapbyte] * 8))
283
+ t += ibm.encode(b' KBI ')
284
+ t += ibm.encode(bytes([track.gapbyte] * 9))
285
+ # DAM
286
+ t += ibm.encode(bytes(track.gap_presync))
287
+ t += ibm.mfm_sync_bytes
288
+ dmark = (ibm.Mark.DDAM if errs.deleted_dam
289
+ else ibm.Mark.DAM)
290
+ t += ibm.encode(bytes([dmark]))
291
+ if idx%3 != 0:
292
+ t += ibm.encode(data[:61])
293
+ elif r == 0:
294
+ t += ibm.encode(data[:512])
295
+ addcrc(t,516)
296
+ else:
297
+ t += ibm.encode(data[0:0x10e])
298
+ addcrc(t,516)
299
+ t += ibm.encode(data[0x110:0x187])
300
+ addcrc(t,516)
301
+ t += ibm.encode(data[0x189:0x200])
302
+ addcrc(t,516)
303
+ t += ibm.encode(bytes([track.gapbyte] * 80))
304
+ return track
305
+
306
+ def from_bytes(self, dat: bytes) -> None:
307
+
308
+ sig, creator, ncyls, nsides, track_sz = struct.unpack(
309
+ '<34s14s2BH', dat[:52])
310
+ if sig[:8] == b'MV - CPC':
311
+ extended = False
312
+ elif sig[:16] == b'EXTENDED CPC DSK':
313
+ extended = True
314
+ else:
315
+ raise error.Fatal('Unrecognised CPC DSK file: bad signature')
316
+
317
+ if extended:
318
+ track_sizes = list(dat[52:52+ncyls*nsides])
319
+ track_sizes = list(map(lambda x: x*256, track_sizes))
320
+ else:
321
+ track_sizes = [track_sz] * (ncyls * nsides)
322
+
323
+ o = 256 # skip disk header and track-size table
324
+ for track_size in track_sizes:
325
+ if track_size == 0:
326
+ continue
327
+ x = struct.unpack('<12s4x8B', dat[o:o+24])
328
+ sig, cyl, head, rate, mode, sec_sz, nsecs, gap_3, filler = x
329
+ error.check(sig == b'Track-Info\r\n',
330
+ 'EDSK: Missing track header')
331
+ error.check((cyl, head) not in self.to_track,
332
+ 'EDSK: Track specified twice')
333
+ bad_crc_clip_data = False
334
+ while True:
335
+ track = EDSKTrack(rate)
336
+ t = track.bytes
337
+ # Post-index gap
338
+ t += ibm.encode(bytes([track.gapbyte] * track.gap_4a))
339
+ # IAM
340
+ t += ibm.encode(bytes(track.gap_presync))
341
+ t += ibm.mfm_iam_sync_bytes
342
+ t += ibm.encode(bytes([ibm.Mark.IAM]))
343
+ t += ibm.encode(bytes([track.gapbyte] * track.gap_1))
344
+ sh = dat[o+24:o+24+8*nsecs]
345
+ data_pos = o + 256 # skip track header and sector-info table
346
+ clippable, ngap3, sectors, idam_included = 0, 0, [], False
347
+ while sh:
348
+ c, h, r, n, stat1, stat2, data_size = struct.unpack(
349
+ '<6BH', sh[:8])
350
+ sh = sh[8:]
351
+ native_size = ibm.sec_sz(n)
352
+ weak = []
353
+ errs = SectorErrors(stat1, stat2)
354
+ num_copies = 0 if errs.data_not_found else 1
355
+ if not extended:
356
+ data_size = ibm.sec_sz(sec_sz)
357
+ sec_data = dat[data_pos:data_pos+data_size]
358
+ data_pos += data_size
359
+ if (extended
360
+ and data_size > native_size
361
+ and errs.data_crc_error
362
+ and (data_size % native_size == 0
363
+ or data_size == 49152)):
364
+ num_copies = (3 if data_size == 49152
365
+ else data_size // native_size)
366
+ data_size //= num_copies
367
+ weak = EDSK.find_weak_ranges(sec_data, data_size)
368
+ sec_data = sec_data[:data_size]
369
+ sectors.append((c,h,r,n,errs,sec_data))
370
+ # IDAM
371
+ if not idam_included:
372
+ t += ibm.encode(bytes(track.gap_presync))
373
+ t += ibm.mfm_sync_bytes
374
+ am = bytes([0xa1, 0xa1, 0xa1, ibm.Mark.IDAM,
375
+ c, h, r, n])
376
+ crc = ibm.crc16.new(am).crcValue
377
+ if errs.id_crc_error:
378
+ crc ^= 0x5555
379
+ am += struct.pack('>H', crc)
380
+ t += ibm.encode(am[3:])
381
+ t += ibm.encode(bytes([track.gapbyte] * track.gap_2))
382
+ # DAM
383
+ gap_included, idam_included = False, False
384
+ if errs.id_crc_error or errs.data_not_found:
385
+ continue
386
+ t += ibm.encode(bytes(track.gap_presync))
387
+ t += ibm.mfm_sync_bytes
388
+ track.weak += [((s+len(t)//2+1)*16, n*16) for s,n in weak]
389
+ dmark = (ibm.Mark.DDAM if errs.deleted_dam
390
+ else ibm.Mark.DAM)
391
+ if errs.data_crc_error:
392
+ if sh:
393
+ # Look for next IDAM
394
+ idam = bytes([0]*12 + [0xa1]*3
395
+ + [ibm.Mark.IDAM])
396
+ idx = sec_data.find(idam)
397
+ else:
398
+ # Last sector: Look for GAP3
399
+ idx = sec_data.find(bytes([track.gapbyte]*8))
400
+ if idx > 0:
401
+ # 2 + gap_3 = CRC + GAP3 (because gap_included)
402
+ clippable += data_size - idx + 2 + gap_3
403
+ if bad_crc_clip_data:
404
+ data_size = idx
405
+ sec_data = sec_data[:data_size]
406
+ gap_included = True
407
+ elif data_size < native_size:
408
+ # Pad short data
409
+ sec_data += bytes(native_size - data_size)
410
+ elif data_size > native_size:
411
+ # Clip long data if it includes pre-sync 00 bytes
412
+ if (sec_data[-13] != 0
413
+ and all([v==0 for v in sec_data[-12:]])):
414
+ # Includes next pre-sync: Clip it.
415
+ sec_data = sec_data[:-12]
416
+ if sh:
417
+ # Look for next IDAM
418
+ idam = bytes([0]*12 + [0xa1]*3 + [ibm.Mark.IDAM]
419
+ + list(sh[:4]))
420
+ idx = sec_data.find(idam)
421
+ if idx > native_size:
422
+ # Sector data includes next IDAM. Output it
423
+ # here and skip it on next iteration.
424
+ t += ibm.encode(bytes([dmark]))
425
+ t += ibm.encode(sec_data[:idx+12])
426
+ t += ibm.mfm_sync_bytes
427
+ t += ibm.encode(sec_data[idx+12+3:])
428
+ idam_included = True
429
+ continue
430
+ # Long data includes CRC and GAP
431
+ gap_included = True
432
+ if gap_included:
433
+ t += ibm.encode(bytes([dmark]))
434
+ t += ibm.encode(sec_data)
435
+ continue
436
+ am = bytes([0xa1, 0xa1, 0xa1, dmark]) + sec_data
437
+ crc = ibm.crc16.new(am).crcValue
438
+ if errs.data_crc_error:
439
+ crc ^= 0x5555
440
+ am += struct.pack('>H', crc)
441
+ t += ibm.encode(am[3:])
442
+ if sh:
443
+ # GAP3 for all but last sector
444
+ t += ibm.encode(bytes([track.gapbyte] * gap_3))
445
+ ngap3 += 1
446
+
447
+ # Special track handlers
448
+ special_track = EDSK._build_8k_track(sectors)
449
+ if special_track is None:
450
+ special_track = EDSK._build_kbi19_track(sectors)
451
+ if special_track is not None:
452
+ track = special_track
453
+ break
454
+
455
+ # The track may be too long to fit: Check for overhang.
456
+ tracklen = int((track.time_per_rev / track.clock) / 16)
457
+ overhang = int(len(t)//2 - tracklen*0.99)
458
+ if overhang <= 0:
459
+ break
460
+
461
+ # Some EDSK tracks with Bad CRC contain a raw dump following
462
+ # the DAM. This can usually be clipped.
463
+ if clippable and not bad_crc_clip_data:
464
+ bad_crc_clip_data = True
465
+ continue
466
+
467
+ # Some EDSK images have bogus GAP3 values. Shrink it if
468
+ # necessary.
469
+ new_gap_3 = -1
470
+ if ngap3 != 0:
471
+ new_gap_3 = gap_3 - math.ceil(overhang / ngap3)
472
+ error.check(new_gap_3 >= 0,
473
+ 'EDSK: Track %d.%d is too long '
474
+ '(%d bits @ GAP3=%d; %d bits @ GAP3=0)'
475
+ % (cyl, head, len(t)*8, gap_3,
476
+ (len(t)//2-gap_3*ngap3)*16))
477
+ #print('EDSK: GAP3 reduced (%d -> %d)' % (gap_3, new_gap_3))
478
+ gap_3 = new_gap_3
479
+
480
+ # Pre-index gap
481
+ track.verify_len = len(track.bytes)*8
482
+ tracklen = int((track.time_per_rev / track.clock) / 16)
483
+ gap = max(40, tracklen - len(t)//2)
484
+ track.bytes += ibm.encode(bytes([track.gapbyte] * gap))
485
+
486
+ # Add the clock buts
487
+ track.bits = bitarray(endian='big')
488
+ track.bits.frombytes(ibm.mfm_encode(track.bytes))
489
+
490
+ # Register the track
491
+ self.to_track[cyl,head] = track
492
+ o += track_size
493
+
494
+
495
+ def get_track(self, cyl: int, side: int) -> Optional[MasterTrack]:
496
+ if (cyl,side) not in self.to_track:
497
+ return None
498
+ return self.to_track[cyl,side].master_track()
499
+
500
+
501
+ def emit_track(self, cyl: int, side: int, track) -> None:
502
+ if isinstance(track, ibm.IBMTrack_Scan):
503
+ track = track.track
504
+ error.check(isinstance(track, ibm.IBMTrack),
505
+ 'EDSK: Cannot create T%d.%d: Not IBM.FM nor IBM.MFM'
506
+ % (cyl, side))
507
+ if not isinstance(track, ibm.IBMTrack_Empty):
508
+ self.to_track[cyl,side] = track
509
+
510
+
511
+ def get_image(self) -> bytes:
512
+
513
+ dat, tdat_list = bytearray(), list()
514
+
515
+ n_side = max(self.to_track.keys(), default=(0,0), key=lambda x:x[1])[1]
516
+ n_side += 1
517
+
518
+ n_cyl = max(self.to_track.keys(), default=(0,0), key=lambda x:x[0])[0]
519
+ n_cyl += 1
520
+
521
+ dat += struct.pack('<34s13sx2B2x',
522
+ b'EXTENDED CPC DSK File\r\nDisk-Info\r\n',
523
+ ('GW %s' % __version__).encode(),
524
+ n_cyl, n_side)
525
+
526
+ for c in range(n_cyl):
527
+
528
+ for h in range(n_side):
529
+
530
+ # Empty track: NUL in Track Size Table
531
+ if (c,h) not in self.to_track:
532
+ dat += b'\x00'
533
+ continue
534
+
535
+ # TODO: Handle FM, and non-DD data rates
536
+ t = self.to_track[c,h]
537
+ assert isinstance(t, ibm.IBMTrack)
538
+ error.check(t.mode is ibm.Mode.MFM,
539
+ 'EDSK: Cannot handle %s track format' % t.mode)
540
+ error.check(0.4e-6 < t.clock < 2.1e-6,
541
+ 'EDSK: Cannot handle %.2fus clock' % (t.clock*1e6))
542
+
543
+ if t.clock < 0.75e-6:
544
+ rate = EDSKRate.ExtendedDensity
545
+ elif t.clock < 1.5e-6:
546
+ rate = EDSKRate.HighDensity
547
+ else:
548
+ # Double Density, but we'll just mark it as default (0)
549
+ rate = 0
550
+
551
+ # Estimate sec_sz from size of first sector only.
552
+ # Estimate gap3 from gap between first two sectors.
553
+ n_sec, sec_sz, gap3 = len(t.sectors), 0, 0
554
+ if n_sec > 0:
555
+ sec_sz = t.sectors[0].idam.n
556
+ gap3 = ibm.MFMGaps.gap3[t.sectors[0].idam.n]
557
+ if n_sec > 1:
558
+ gap3 = t.sectors[1].start - t.sectors[0].end
559
+ gap3 = max(0, round(gap3 / 16) - t.gap_presync)
560
+ tdat = struct.pack('<12s4x8B',
561
+ b'Track-Info\r\n', c, h, rate, 0,
562
+ sec_sz, n_sec, gap3, 0xe5)
563
+
564
+ # Track Information Block
565
+ for s in t.sectors:
566
+ tdat += struct.pack('<6BH',
567
+ s.idam.c, s.idam.h, s.idam.r, s.idam.n,
568
+ 0, 0, ibm.sec_sz(s.idam.n))
569
+ tdat += bytes(-len(tdat) & 255)
570
+
571
+ # Track Data
572
+ for s in t.sectors:
573
+ tdat += s.dam.data[:]
574
+
575
+ # Track Size Table
576
+ assert (len(tdat) & 255) == 0
577
+ dat += struct.pack('B', len(tdat) // 256)
578
+
579
+ tdat_list.append(tdat)
580
+
581
+ # Pad and concatenate the whole image together.
582
+ dat += bytes(-len(dat) & 255)
583
+ for tdat in tdat_list:
584
+ dat += tdat
585
+
586
+ return dat
587
+
588
+
589
+ # Local variables:
590
+ # python-indent: 4
591
+ # End:
@@ -0,0 +1,23 @@
1
+ # greaseweazle/image/fd.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
+ # FD is a widely used format in the Thomson MO/TO community, directly
9
+ # supported by emulators such as DCMOTO.
10
+ #
11
+ # The main difference between IMG and FD is that FD includes the two sides of
12
+ # a double-sided disk sequentially, rather than interleaved per-cylinder.
13
+ # That is, all tracks of side 0 are stored before all tracks of side 1.
14
+
15
+ from diskstack._vendor.greaseweazle.image.img import IMG
16
+
17
+ class FD(IMG):
18
+ default_format = 'thomson.1s320' # Most commonly used format
19
+ sequential = True # whole side 0 /then/ whole side 1
20
+
21
+ # Local variables:
22
+ # python-indent: 4
23
+ # End:
@@ -0,0 +1,41 @@
1
+ # greaseweazle/image/fdi.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 struct
9
+
10
+ from diskstack._vendor.greaseweazle import error
11
+ from diskstack._vendor.greaseweazle.image.img import IMG
12
+ from .image import Image
13
+
14
+ class FDI(IMG):
15
+ default_format = 'pc98.2hd'
16
+ read_only = True
17
+
18
+ def from_bytes(self, dat: bytes) -> None:
19
+
20
+ (magic, fdd_type, header_size, sector_size, sectors_per_track,
21
+ sides, tracks) = struct.unpack('<LLL4xLLLL', dat[:32])
22
+ error.check(magic == 0, 'FDI: Not a FDI file.')
23
+ error.check(fdd_type == 0x90, 'FDI: Unsupported format.')
24
+ error.check(sector_size == 1024, 'FDI: Unsupported sector size.')
25
+ error.check(sectors_per_track == 8,
26
+ 'FDI: Unsupported number of sectors per track.')
27
+ error.check(sides == 2, 'FDI: Unsupported number of sides.')
28
+ error.check(tracks == 77, 'FDI: Unsupported number of tracks.')
29
+
30
+ pos = header_size
31
+ for cyl, head in self.track_list():
32
+ if self.sides_swapped:
33
+ head ^= 1
34
+ track = self.fmt.mk_track(cyl, head)
35
+ if track is not None:
36
+ pos += track.set_img_track(dat[pos:])
37
+ self.to_track[cyl,head] = track
38
+
39
+ # Local variables:
40
+ # python-indent: 4
41
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/hdm.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 HDM(IMG):
11
+ default_format = 'pc98.2hd'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End: