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,130 @@
|
|
|
1
|
+
# greaseweazle/image/image.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 Optional, List, Dict
|
|
10
|
+
|
|
11
|
+
import os
|
|
12
|
+
|
|
13
|
+
from diskstack._vendor.greaseweazle import error
|
|
14
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
15
|
+
from diskstack._vendor.greaseweazle.flux import HasFlux
|
|
16
|
+
|
|
17
|
+
OptDict = Dict[str,str]
|
|
18
|
+
|
|
19
|
+
class ImageOpts:
|
|
20
|
+
r_settings: List[str] = [] # r_set()
|
|
21
|
+
w_settings: List[str] = [] # w_set()
|
|
22
|
+
a_settings: List[str] = [] # r_set(), w_set()
|
|
23
|
+
|
|
24
|
+
def _set(self, filename: str, opt: str, val: str,
|
|
25
|
+
settings: List[str]) -> None:
|
|
26
|
+
error.check(opt in settings,
|
|
27
|
+
"%s: Invalid file option: %s\n" % (filename, opt)
|
|
28
|
+
+ 'Valid options: '
|
|
29
|
+
+ (', '.join(settings) if settings else '<none>'))
|
|
30
|
+
setattr(self, opt, val)
|
|
31
|
+
|
|
32
|
+
def r_set(self, filename: str, opt: str, val: str) -> None:
|
|
33
|
+
self._set(filename, opt, val, self.a_settings + self.r_settings)
|
|
34
|
+
|
|
35
|
+
def w_set(self, filename: str, opt: str, val: str) -> None:
|
|
36
|
+
self._set(filename, opt, val, self.a_settings + self.w_settings)
|
|
37
|
+
|
|
38
|
+
class Image:
|
|
39
|
+
|
|
40
|
+
filename: str
|
|
41
|
+
noclobber = False
|
|
42
|
+
default_format: Optional[str] = None
|
|
43
|
+
read_only = False
|
|
44
|
+
write_on_ctrl_c = False
|
|
45
|
+
opts = ImageOpts() # empty
|
|
46
|
+
|
|
47
|
+
def __init__(self, name: str, fmt) -> None:
|
|
48
|
+
raise NotImplementedError
|
|
49
|
+
|
|
50
|
+
def apply_r_opts(self, opts: OptDict) -> None:
|
|
51
|
+
for opt, val in opts.items():
|
|
52
|
+
self.opts.r_set(self.filename, opt, val)
|
|
53
|
+
|
|
54
|
+
def apply_w_opts(self, opts: OptDict) -> None:
|
|
55
|
+
for opt, val in opts.items():
|
|
56
|
+
self.opts.w_set(self.filename, opt, val)
|
|
57
|
+
|
|
58
|
+
## Context manager for image objects created using .to_file()
|
|
59
|
+
|
|
60
|
+
def __enter__(self) -> Image:
|
|
61
|
+
self.file = open(self.filename, ('wb','xb')[self.noclobber])
|
|
62
|
+
return self
|
|
63
|
+
|
|
64
|
+
def __exit__(self, type, value, tb):
|
|
65
|
+
save = (type is None or
|
|
66
|
+
(type is KeyboardInterrupt and self.write_on_ctrl_c))
|
|
67
|
+
try:
|
|
68
|
+
if save:
|
|
69
|
+
# No error: Normal writeout.
|
|
70
|
+
self.file.write(self.get_image())
|
|
71
|
+
finally:
|
|
72
|
+
# Always close the file.
|
|
73
|
+
self.file.close()
|
|
74
|
+
if not save:
|
|
75
|
+
# An error occurred: We remove the target file.
|
|
76
|
+
os.remove(self.filename)
|
|
77
|
+
|
|
78
|
+
## Default .to_file() constructor
|
|
79
|
+
@classmethod
|
|
80
|
+
def to_file(cls, name: str, fmt: Optional[codec.DiskDef],
|
|
81
|
+
noclobber: bool, opts: OptDict) -> Image:
|
|
82
|
+
error.check(not cls.read_only,
|
|
83
|
+
"%s: Cannot create %s image files" % (name, cls.__name__))
|
|
84
|
+
obj = cls(name, fmt)
|
|
85
|
+
obj.noclobber = noclobber
|
|
86
|
+
obj.apply_w_opts(opts)
|
|
87
|
+
return obj
|
|
88
|
+
|
|
89
|
+
## Default .from_file() constructor
|
|
90
|
+
@classmethod
|
|
91
|
+
def from_file(cls, name: str, fmt: Optional[codec.DiskDef],
|
|
92
|
+
opts: OptDict) -> Image:
|
|
93
|
+
obj = cls(name, fmt)
|
|
94
|
+
obj.apply_r_opts(opts)
|
|
95
|
+
with open(name, "rb") as f:
|
|
96
|
+
obj.from_bytes(f.read())
|
|
97
|
+
return obj
|
|
98
|
+
|
|
99
|
+
## Used by default .from_file constructor
|
|
100
|
+
def from_bytes(self, dat: bytes) -> None:
|
|
101
|
+
raise NotImplementedError
|
|
102
|
+
|
|
103
|
+
# Maximum non-empty cylinder on each head, or -1 if no cylinders exist.
|
|
104
|
+
# Returns a list of integers, indexed by head.
|
|
105
|
+
def max_cylinder(self):
|
|
106
|
+
r = list()
|
|
107
|
+
for h in range(2):
|
|
108
|
+
for c in range(100, -2, -1):
|
|
109
|
+
if c < 0 or self.get_track(c,h) is not None:
|
|
110
|
+
r.append(c)
|
|
111
|
+
break
|
|
112
|
+
return r
|
|
113
|
+
|
|
114
|
+
## Above methods and class variables can be overridden by subclasses.
|
|
115
|
+
## Additionally, subclasses must provide following public interfaces:
|
|
116
|
+
|
|
117
|
+
def get_track(self, cyl: int, side: int) -> Optional[HasFlux]:
|
|
118
|
+
raise NotImplementedError
|
|
119
|
+
|
|
120
|
+
## Write support (if not cls.read_only):
|
|
121
|
+
def emit_track(self, cyl: int, side: int, track: HasFlux):
|
|
122
|
+
raise NotImplementedError
|
|
123
|
+
## Plus get_image, or __enter__ / __exit__
|
|
124
|
+
def get_image(self) -> bytes:
|
|
125
|
+
raise NotImplementedError
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
# Local variables:
|
|
129
|
+
# python-indent: 4
|
|
130
|
+
# End:
|
|
@@ -0,0 +1,201 @@
|
|
|
1
|
+
# greaseweazle/image/imd.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 datetime, struct
|
|
11
|
+
|
|
12
|
+
from diskstack._vendor.greaseweazle import __version__
|
|
13
|
+
from diskstack._vendor.greaseweazle import error
|
|
14
|
+
from diskstack._vendor.greaseweazle.codec.ibm import ibm
|
|
15
|
+
from .image import Image
|
|
16
|
+
|
|
17
|
+
class IMDMode:
|
|
18
|
+
FM_500kbps = 0
|
|
19
|
+
FM_300kbps = 1
|
|
20
|
+
FM_250kbps = 2
|
|
21
|
+
MFM_500kbps = 3
|
|
22
|
+
MFM_300kbps = 4
|
|
23
|
+
MFM_250kbps = 5
|
|
24
|
+
|
|
25
|
+
class IMD(Image):
|
|
26
|
+
|
|
27
|
+
def __init__(self, name: str, _fmt):
|
|
28
|
+
self.to_track: Dict[Tuple[int,int],ibm.IBMTrack_Fixed] = dict()
|
|
29
|
+
self.filename = name
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def from_bytes(self, dat: bytes) -> None:
|
|
33
|
+
|
|
34
|
+
# Check and strip the header
|
|
35
|
+
sig, = struct.unpack('4s', dat[:4])
|
|
36
|
+
error.check(sig == b'IMD ', 'Unrecognised IMD file: bad signature')
|
|
37
|
+
for i,x in enumerate(dat):
|
|
38
|
+
if x == 0x1a:
|
|
39
|
+
break
|
|
40
|
+
error.check(x == 0x1a, 'IMD: No comment terminator found')
|
|
41
|
+
|
|
42
|
+
# We will adjust this as we go
|
|
43
|
+
rpm = 300
|
|
44
|
+
|
|
45
|
+
i += 1
|
|
46
|
+
while i < len(dat)-5:
|
|
47
|
+
mode, cyl, head, nsec, sec_n = struct.unpack('5B', dat[i:i+5])
|
|
48
|
+
i += 5
|
|
49
|
+
error.check(0 <= sec_n <= 6, 'IMD: Bad sector size %x' % sec_n)
|
|
50
|
+
secsz = 128 << sec_n
|
|
51
|
+
|
|
52
|
+
has_cyl_map = (head & 0x80) != 0
|
|
53
|
+
has_head_map = (head & 0x40) != 0
|
|
54
|
+
head &= 0x3f
|
|
55
|
+
error.check(0 <= head <= 1, 'IMD: Bad head value %x' % head)
|
|
56
|
+
|
|
57
|
+
if mode == IMDMode.FM_250kbps or mode == IMDMode.FM_300kbps:
|
|
58
|
+
fmt = ibm.IBMTrack_FixedDef('ibm.fm')
|
|
59
|
+
fmt.rate = 125
|
|
60
|
+
elif mode == IMDMode.FM_500kbps:
|
|
61
|
+
fmt = ibm.IBMTrack_FixedDef('ibm.fm')
|
|
62
|
+
if nsec == 26: rpm = 360 # 8-inch disk
|
|
63
|
+
fmt.rate = 250
|
|
64
|
+
elif mode == IMDMode.MFM_250kbps or mode == IMDMode.MFM_300kbps:
|
|
65
|
+
fmt = ibm.IBMTrack_FixedDef('ibm.mfm')
|
|
66
|
+
fmt.rate = 250
|
|
67
|
+
elif mode == IMDMode.MFM_500kbps:
|
|
68
|
+
fmt = ibm.IBMTrack_FixedDef('ibm.mfm')
|
|
69
|
+
if nsec == 26: rpm = 360 # 8-inch disk
|
|
70
|
+
fmt.rate = 500
|
|
71
|
+
else:
|
|
72
|
+
raise error.Fatal('IMD: Unrecognised track mode %x' % mode)
|
|
73
|
+
|
|
74
|
+
fmt.rpm = rpm
|
|
75
|
+
fmt.secs, fmt.sz = nsec, [sec_n]
|
|
76
|
+
fmt.finalise()
|
|
77
|
+
t = fmt.mk_track(cyl, head)
|
|
78
|
+
|
|
79
|
+
rmap = dat[i:i+nsec]
|
|
80
|
+
i += nsec
|
|
81
|
+
if has_cyl_map:
|
|
82
|
+
cmap = dat[i:i+nsec]
|
|
83
|
+
i += nsec
|
|
84
|
+
if has_head_map:
|
|
85
|
+
hmap = dat[i:i+nsec]
|
|
86
|
+
i += nsec
|
|
87
|
+
|
|
88
|
+
for nr,s in enumerate(t.sectors):
|
|
89
|
+
s.crc = s.idam.crc = s.dam.crc = 0
|
|
90
|
+
s.idam.r = rmap[nr]
|
|
91
|
+
if has_cyl_map:
|
|
92
|
+
s.idam.c = cmap[nr]
|
|
93
|
+
if has_head_map:
|
|
94
|
+
s.idam.h = hmap[nr]
|
|
95
|
+
rec = dat[i]
|
|
96
|
+
i += 1
|
|
97
|
+
error.check(0 <= rec <= 8,
|
|
98
|
+
'IMD: Unexpected sector code %x' % rec)
|
|
99
|
+
if rec == 0:
|
|
100
|
+
s.crc = s.dam.crc = 0xffff
|
|
101
|
+
continue # Data unavailable
|
|
102
|
+
rec -= 1
|
|
103
|
+
if rec&1:
|
|
104
|
+
s.dam.data = bytes([dat[i]] * secsz)
|
|
105
|
+
i += 1
|
|
106
|
+
else:
|
|
107
|
+
s.dam.data = dat[i:i+secsz]
|
|
108
|
+
i += secsz
|
|
109
|
+
if rec&2:
|
|
110
|
+
s.dam.mark = ibm.Mark.DDAM
|
|
111
|
+
if rec&4:
|
|
112
|
+
s.crc = s.dam.crc = 0xffff
|
|
113
|
+
|
|
114
|
+
self.to_track[cyl,head] = t
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def get_track(self, cyl: int, side: int) -> Optional[ibm.IBMTrack_Fixed]:
|
|
118
|
+
if (cyl,side) not in self.to_track:
|
|
119
|
+
return None
|
|
120
|
+
return self.to_track[cyl,side]
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def emit_track(self, cyl: int, side: int, track) -> None:
|
|
124
|
+
if isinstance(track, ibm.IBMTrack_Scan):
|
|
125
|
+
track = track.track
|
|
126
|
+
error.check(isinstance(track, ibm.IBMTrack),
|
|
127
|
+
'IMD: Cannot create T%d.%d: Not IBM.FM nor IBM.MFM'
|
|
128
|
+
% (cyl, side))
|
|
129
|
+
if not isinstance(track, ibm.IBMTrack_Empty):
|
|
130
|
+
self.to_track[cyl,side] = track
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
def get_image(self) -> bytes:
|
|
134
|
+
|
|
135
|
+
tdat = bytearray()
|
|
136
|
+
|
|
137
|
+
now = datetime.datetime.now()
|
|
138
|
+
sig = ('IMD 1.17: %s\r\nGreaseweazle %s\r\n\x1a'
|
|
139
|
+
% (now.strftime('%d/%m/%Y %H:%M:%S'), __version__))
|
|
140
|
+
tdat += sig.encode()
|
|
141
|
+
|
|
142
|
+
for (c,h),t in sorted(self.to_track.items()):
|
|
143
|
+
if t.mode is ibm.Mode.FM:
|
|
144
|
+
if t.clock < 3.0e-6:
|
|
145
|
+
mode = IMDMode.FM_500kbps # High Rate
|
|
146
|
+
else:
|
|
147
|
+
mode = IMDMode.FM_250kbps # 300 RPM
|
|
148
|
+
else:
|
|
149
|
+
assert t.mode is ibm.Mode.MFM
|
|
150
|
+
if t.clock < 1.5e-6:
|
|
151
|
+
mode = IMDMode.MFM_500kbps # High Rate
|
|
152
|
+
else:
|
|
153
|
+
mode = IMDMode.MFM_250kbps # 300 RPM
|
|
154
|
+
cyl, head, nsec, sec_n = c, h, 0, None
|
|
155
|
+
rmap, cmap, hmap = [], [], []
|
|
156
|
+
for s in t.sectors:
|
|
157
|
+
if not isinstance(s, ibm.Sector):
|
|
158
|
+
continue
|
|
159
|
+
nsec += 1
|
|
160
|
+
error.check(s.idam.n == sec_n or sec_n is None,
|
|
161
|
+
'IMD: Cannot create T%d.%d: Sectors vary in size'
|
|
162
|
+
% (c,h))
|
|
163
|
+
sec_n = s.idam.n
|
|
164
|
+
rmap.append(s.idam.r)
|
|
165
|
+
cmap.append(s.idam.c)
|
|
166
|
+
hmap.append(s.idam.h)
|
|
167
|
+
if s.idam.c != c:
|
|
168
|
+
head |= 0x80
|
|
169
|
+
if s.idam.h != h:
|
|
170
|
+
head |= 0x40
|
|
171
|
+
if sec_n is None:
|
|
172
|
+
sec_n = 0
|
|
173
|
+
secsz = 128 << sec_n
|
|
174
|
+
tdat += struct.pack('5B', mode, cyl, head, nsec, sec_n)
|
|
175
|
+
tdat += bytes(rmap)
|
|
176
|
+
if head & 0x80:
|
|
177
|
+
tdat += bytes(cmap)
|
|
178
|
+
if head & 0x40:
|
|
179
|
+
tdat += bytes(hmap)
|
|
180
|
+
for s in t.sectors:
|
|
181
|
+
if not isinstance(s, ibm.Sector):
|
|
182
|
+
continue
|
|
183
|
+
rec = 0
|
|
184
|
+
if s.dam.data.count(s.dam.data[0]) == secsz:
|
|
185
|
+
rec |= 1
|
|
186
|
+
if s.dam.mark == ibm.Mark.DDAM:
|
|
187
|
+
rec |= 2
|
|
188
|
+
if s.dam.crc != 0:
|
|
189
|
+
rec |= 4
|
|
190
|
+
tdat += bytes([rec+1])
|
|
191
|
+
if rec & 1:
|
|
192
|
+
tdat += s.dam.data[:1]
|
|
193
|
+
else:
|
|
194
|
+
tdat += s.dam.data[:]
|
|
195
|
+
|
|
196
|
+
return tdat
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
# Local variables:
|
|
200
|
+
# python-indent: 4
|
|
201
|
+
# End:
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# greaseweazle/image/img.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, Any
|
|
9
|
+
|
|
10
|
+
from diskstack._vendor.greaseweazle import error
|
|
11
|
+
from diskstack._vendor.greaseweazle.codec import codec
|
|
12
|
+
from diskstack._vendor.greaseweazle.flux import HasFlux
|
|
13
|
+
from .image import Image, OptDict
|
|
14
|
+
|
|
15
|
+
class IMG(Image):
|
|
16
|
+
|
|
17
|
+
sides_swapped = False
|
|
18
|
+
sequential = False
|
|
19
|
+
min_cyls: Optional[int] = None
|
|
20
|
+
|
|
21
|
+
def __init__(self, name: str, fmt):
|
|
22
|
+
self.to_track: Dict[Tuple[int,int],codec.Codec] = dict()
|
|
23
|
+
error.check(fmt is not None, """\
|
|
24
|
+
Sector image requires a disk format to be specified""")
|
|
25
|
+
self.filename = name
|
|
26
|
+
self.fmt: codec.DiskDef = fmt
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def track_list(self):
|
|
30
|
+
t, l = self.fmt.tracks, []
|
|
31
|
+
if self.sequential:
|
|
32
|
+
for h in t.heads:
|
|
33
|
+
for c in t.cyls:
|
|
34
|
+
l.append((c,h))
|
|
35
|
+
else:
|
|
36
|
+
for c in t.cyls:
|
|
37
|
+
for h in t.heads:
|
|
38
|
+
l.append((c,h))
|
|
39
|
+
return l
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def from_bytes(self, dat: bytes) -> None:
|
|
43
|
+
pos = 0
|
|
44
|
+
for (cyl, head) in self.track_list():
|
|
45
|
+
if self.sides_swapped:
|
|
46
|
+
head ^= 1
|
|
47
|
+
track = self.fmt.mk_track(cyl, head)
|
|
48
|
+
if track is not None:
|
|
49
|
+
pos += track.set_img_track(dat[pos:])
|
|
50
|
+
self.to_track[cyl,head] = track
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def get_track(self, cyl: int, side: int) -> Optional[codec.Codec]:
|
|
54
|
+
if (cyl,side) not in self.to_track:
|
|
55
|
+
return None
|
|
56
|
+
return self.to_track[cyl,side]
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def emit_track(self, cyl: int, side: int, track) -> None:
|
|
60
|
+
self.to_track[cyl,side] = track
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def get_image(self) -> bytes:
|
|
64
|
+
|
|
65
|
+
tdat = bytearray()
|
|
66
|
+
|
|
67
|
+
# If min_cyls is specified, only emit extra cylinders if there is
|
|
68
|
+
# valid data.
|
|
69
|
+
max_cyl = None
|
|
70
|
+
if self.min_cyls is not None:
|
|
71
|
+
max_cyl = self.min_cyls - 1
|
|
72
|
+
for (cyl, head) in self.track_list():
|
|
73
|
+
if cyl > max_cyl and (cyl,head) in self.to_track:
|
|
74
|
+
track = self.to_track[cyl,head]
|
|
75
|
+
if track.nr_missing() < track.nsec:
|
|
76
|
+
max_cyl = cyl
|
|
77
|
+
|
|
78
|
+
for (cyl, head) in self.track_list():
|
|
79
|
+
if max_cyl is not None and cyl > max_cyl:
|
|
80
|
+
break
|
|
81
|
+
if self.sides_swapped:
|
|
82
|
+
head ^= 1
|
|
83
|
+
if (cyl,head) in self.to_track:
|
|
84
|
+
t = self.to_track[cyl,head]
|
|
85
|
+
else:
|
|
86
|
+
_t = self.fmt.mk_track(cyl, head)
|
|
87
|
+
assert _t is not None # mypy
|
|
88
|
+
t = _t
|
|
89
|
+
tdat += t.get_img_track()
|
|
90
|
+
|
|
91
|
+
return tdat
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
class IMG_AutoFormat(IMG):
|
|
95
|
+
|
|
96
|
+
@staticmethod
|
|
97
|
+
def format_from_file(name: str) -> str:
|
|
98
|
+
raise NotImplementedError
|
|
99
|
+
|
|
100
|
+
@classmethod
|
|
101
|
+
def from_file(cls, name: str, fmt: Optional[codec.DiskDef],
|
|
102
|
+
opts: OptDict) -> Image:
|
|
103
|
+
if fmt is None:
|
|
104
|
+
format_str = cls.format_from_file(name)
|
|
105
|
+
fmt = codec.get_diskdef(format_str)
|
|
106
|
+
print(f'{cls.__name__}: Image format {format_str}')
|
|
107
|
+
return super().from_file(name, fmt, opts)
|
|
108
|
+
|
|
109
|
+
# Local variables:
|
|
110
|
+
# python-indent: 4
|
|
111
|
+
# End:
|