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,28 @@
1
+ # greaseweazle/image/acorn.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 SSD(IMG):
11
+ default_format = 'acorn.dfs.ss'
12
+ sequential = True
13
+
14
+ class DSD(IMG):
15
+ default_format = 'acorn.dfs.ds'
16
+
17
+ class ADS(IMG):
18
+ default_format = 'acorn.adfs.160'
19
+
20
+ class ADM(IMG):
21
+ default_format = 'acorn.adfs.320'
22
+
23
+ class ADL(IMG):
24
+ default_format = 'acorn.adfs.640'
25
+
26
+ # Local variables:
27
+ # python-indent: 4
28
+ # End:
@@ -0,0 +1,15 @@
1
+ # greaseweazle/image/adf.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 ADF(IMG):
11
+ default_format = 'amiga.amigados'
12
+
13
+ # Local variables:
14
+ # python-indent: 4
15
+ # End:
@@ -0,0 +1,18 @@
1
+ # greaseweazle/image/apple2.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 DO(IMG):
11
+ default_format = 'apple2.appledos.140'
12
+
13
+ class PO(IMG):
14
+ default_format = 'apple2.prodos.140'
15
+
16
+ # Local variables:
17
+ # python-indent: 4
18
+ # End:
@@ -0,0 +1,107 @@
1
+ # greaseweazle/tools/apridisk.py
2
+ #
3
+ # 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 enum import Enum
9
+
10
+ from diskstack._vendor.greaseweazle import error
11
+ from diskstack._vendor.greaseweazle.codec import codec
12
+ from diskstack._vendor.greaseweazle.image.img import IMG
13
+
14
+
15
+ class Apridisk(IMG):
16
+
17
+ def from_bytes(self, dat: bytes) -> None:
18
+ error.check(len(dat) >= 128, "Apridisk file missing 128 byte header")
19
+ dat = dat[128:]
20
+
21
+ records = ApridiskRecord.split_apridisk_file(dat, self.fmt)
22
+ records.sort(key = lambda h : h.ord)
23
+
24
+ data = bytes([i for r in records for i in r.expand_record()])
25
+ super().from_bytes(data)
26
+
27
+ class ApridiskRecord:
28
+ def __init__(self, data: bytes, fmt: codec.DiskDef):
29
+ error.check(len(data) >= 16, "Apridisk record contains no section header")
30
+
31
+ self.fmt = fmt
32
+ self.itm_type = ApridiskSecType.from_bytes(data[:4])
33
+ self.header_size = int.from_bytes(data[6:8], byteorder="little")
34
+ self.data_size = int.from_bytes(data[8:12], byteorder="little")
35
+
36
+ self.record_size = self.header_size + self.data_size
37
+ error.check(len(data) >= self.record_size,
38
+ f"Apridisk file ends before end of record. ")
39
+ self.data = data[self.header_size : self.record_size]
40
+
41
+ self._match_pos(data)
42
+ self._match_compression(data)
43
+
44
+ @staticmethod
45
+ def split_apridisk_file(file: bytes, fmt: codec.DiskDef) -> list[ApridiskRecord]:
46
+ res: list[ApridiskRecord] = []
47
+ byte = 0
48
+ while file:
49
+ record = ApridiskRecord(file, fmt)
50
+ res.append(record)
51
+ byte += record.record_size
52
+ file = file[record.record_size:]
53
+ return res
54
+
55
+ def expand_record(self) -> bytes:
56
+ if self.itm_type != ApridiskSecType.SECTOR:
57
+ return b''
58
+ if self.compression:
59
+ return bytes([self.byte for _ in range(0, self.count)])
60
+ return self.data
61
+
62
+ def _match_compression(self, data: bytes):
63
+ compression = int.from_bytes(data[4:6], byteorder="little")
64
+ error.check(compression in [0x9E90, 0x3E5A],
65
+ f"Apridisk unknown compression scheme: {compression}")
66
+ self.compression = compression == 0x3E5A
67
+
68
+ if self.compression:
69
+ error.check(len(self.data) == 3,
70
+ f"Apridisk compressed section length must be 3, length: {len(self.data)}")
71
+ self.count = int.from_bytes(self.data[0:2], byteorder="little")
72
+ self.byte = self.data[2]
73
+
74
+ def _match_pos(self, data: bytes):
75
+ error.check(data[12] in range(0, self.fmt.heads or 2),
76
+ f"Apridisk heads out of range (max {self.fmt.heads or 2}): {hex(data[12])}")
77
+ self.head = data[12]
78
+
79
+ error.check(data[13] in range(1, 19) or self.itm_type != ApridiskSecType.SECTOR,
80
+ f"Apridisk sector out of range (must be 1-9): {data[13]}")
81
+ self.sector = data[13]
82
+
83
+ cyl = int.from_bytes(data[14:16], byteorder="little")
84
+ error.check(cyl in range(0, self.fmt.cyls or 80),
85
+ f"Apridisk cylinder out of range for chosen format (max {self.fmt.cyls}): {cyl}")
86
+ self.cyl = cyl
87
+
88
+ # Used for sorting
89
+ self.ord = self.sector + (self.head * 20) + (self.cyl * 100)
90
+
91
+ class ApridiskSecType(Enum):
92
+ DELETED = 0xE31D0000
93
+ SECTOR = 0xE31D0001
94
+ COMMENT = 0xE31D0002
95
+ CREATOR = 0xE31D0003
96
+
97
+ @staticmethod
98
+ def from_bytes(b: bytes) -> ApridiskSecType:
99
+ num = int.from_bytes(b[:4], byteorder="little")
100
+ try:
101
+ return ApridiskSecType(num)
102
+ except:
103
+ raise error.Fatal(f"Unknown Apridisk sector type: {hex(num)}")
104
+
105
+ # Local variables:
106
+ # python-indent: 4
107
+ # End:
@@ -0,0 +1,464 @@
1
+ # greaseweazle/image/caps.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 cast, List, Tuple, Optional, Generator
10
+
11
+ import os, sys
12
+ import platform
13
+ import ctypes as ct
14
+ import itertools as it
15
+ from bitarray import bitarray
16
+ from diskstack._vendor.greaseweazle.track import MasterTrack, PLLTrack
17
+ from diskstack._vendor.greaseweazle.flux import Flux
18
+ from diskstack._vendor.greaseweazle import error
19
+ from .image import Image, OptDict
20
+
21
+ class CapsDateTimeExt(ct.Structure):
22
+ _pack_ = 1
23
+ _fields_ = [
24
+ ('year', ct.c_uint),
25
+ ('month', ct.c_uint),
26
+ ('day', ct.c_uint),
27
+ ('hour', ct.c_uint),
28
+ ('min', ct.c_uint),
29
+ ('sec', ct.c_uint),
30
+ ('tick', ct.c_uint)]
31
+
32
+ class CapsImageInfo(ct.Structure):
33
+ platform_name = [
34
+ "N/A", "Amiga", "Atari ST", "IBM PC", "Amstrad CPC",
35
+ "Spectrum", "Sam Coupe", "Archimedes", "C64", "Atari (8-bit)" ]
36
+ _pack_ = 1
37
+ _fields_ = [
38
+ ('type', ct.c_uint), # image type
39
+ ('release', ct.c_uint), # release ID
40
+ ('revision', ct.c_uint), # release revision ID
41
+ ('mincylinder', ct.c_uint), # lowest cylinder number
42
+ ('maxcylinder', ct.c_uint), # highest cylinder number
43
+ ('minhead', ct.c_uint), # lowest head number
44
+ ('maxhead', ct.c_uint), # highest head number
45
+ ('crdt', CapsDateTimeExt), # image creation date.time
46
+ ('platform', ct.c_uint * 4)] # intended platform(s)
47
+
48
+ class CapsTrackInfoT2(ct.Structure):
49
+ _pack_ = 1
50
+ _fields_ = [
51
+ ('type', ct.c_uint), # track type
52
+ ('cylinder', ct.c_uint), # cylinder#
53
+ ('head', ct.c_uint), # head#
54
+ ('sectorcnt', ct.c_uint), # available sectors
55
+ ('sectorsize', ct.c_uint), # sector size, unused
56
+ ('trackbuf', ct.POINTER(ct.c_ubyte)), # track buffer memory
57
+ ('tracklen', ct.c_uint), # track buffer memory length
58
+ ('timelen', ct.c_uint), # timing buffer length
59
+ ('timebuf', ct.POINTER(ct.c_uint)), # timing buffer
60
+ ('overlap', ct.c_int), # overlap position
61
+ ('startbit', ct.c_uint), # start position of the decoding
62
+ ('wseed', ct.c_uint), # weak bit generator data
63
+ ('weakcnt', ct.c_uint)] # number of weak data areas
64
+
65
+ class CapsSectorInfo(ct.Structure):
66
+ _pack_ = 1
67
+ _fields_ = [
68
+ ('descdatasize', ct.c_uint), # data size in bits from IPF descriptor
69
+ ('descgapsize', ct.c_uint), # gap size in bits from IPF descriptor
70
+ ('datasize', ct.c_uint), # data size in bits from decoder
71
+ ('gapsize', ct.c_uint), # gap size in bits from decoder
72
+ ('datastart', ct.c_uint), # data start pos in bits from decoder
73
+ ('gapstart', ct.c_uint), # gap start pos in bits from decoder
74
+ ('gapsizews0', ct.c_uint), # gap size before write splice
75
+ ('gapsizews1', ct.c_uint), # gap size after write splice
76
+ ('gapws0mode', ct.c_uint), # gap size mode before write splice
77
+ ('gapws1mode', ct.c_uint), # gap size mode after write splice
78
+ ('celltype', ct.c_uint), # bitcell type
79
+ ('enctype', ct.c_uint)] # encoder type
80
+
81
+ class CapsDataInfo(ct.Structure):
82
+ _pack_ = 1
83
+ _fields_ = [
84
+ ('type', ct.c_uint), # data type
85
+ ('start', ct.c_uint), # start position
86
+ ('size', ct.c_uint)] # size in bits
87
+
88
+ class DI_LOCK:
89
+ INDEX = 1<<0
90
+ ALIGN = 1<<1
91
+ DENVAR = 1<<2
92
+ DENAUTO = 1<<3
93
+ DENNOISE = 1<<4
94
+ NOISE = 1<<5
95
+ NOISEREV = 1<<6
96
+ MEMREF = 1<<7
97
+ UPDATEFD = 1<<8
98
+ TYPE = 1<<9
99
+ DENALT = 1<<10
100
+ OVLBIT = 1<<11
101
+ TRKBIT = 1<<12
102
+ NOUPDATE = 1<<13
103
+ SETWSEED = 1<<14
104
+ def_flags = (DENVAR | UPDATEFD | NOUPDATE | TYPE | OVLBIT | TRKBIT)
105
+
106
+ RangeList = List[Tuple[int,int]]
107
+
108
+ class CAPSTrackInfo(CapsTrackInfoT2):
109
+
110
+ class NoTrack(Exception):
111
+ pass
112
+
113
+ def __init__(self, image: CAPS, cyl: int, head: int) -> None:
114
+ pi = image.pi
115
+ if head < pi.minhead or head > pi.maxhead:
116
+ raise CAPSTrackInfo.NoTrack()
117
+ if cyl < pi.mincylinder or cyl > pi.maxcylinder:
118
+ raise CAPSTrackInfo.NoTrack()
119
+
120
+ ti = self
121
+ CapsTrackInfoT2.__init__(ti, 2)
122
+ res = image.lib.CAPSLockTrack(ct.byref(ti), image.iid,
123
+ cyl, head, DI_LOCK.def_flags)
124
+ error.check(res == 0, "Could not lock CAPS track %d.%d" % (cyl, head))
125
+
126
+ if not ti.trackbuf:
127
+ raise CAPSTrackInfo.NoTrack() # unformatted/empty
128
+
129
+ carray_type = ct.c_ubyte * ((ti.tracklen+7)//8)
130
+ carray = carray_type.from_address(
131
+ ct.addressof(ti.trackbuf.contents))
132
+ bits = bitarray(endian='big')
133
+ bits.frombytes(bytes(carray))
134
+ bits = bits = bits[:ti.tracklen]
135
+
136
+ ticks: Optional[List[float]] = None
137
+ if ti.timebuf:
138
+ carray_type = ct.c_uint * ti.timelen
139
+ carray = carray_type.from_address(
140
+ ct.addressof(ti.timebuf.contents))
141
+ # Unpack the per-byte timing info into per-bitcell
142
+ ticks = []
143
+ for i in carray:
144
+ for j in range(8):
145
+ ticks.append(i)
146
+ # Pad the timing info with normal cell lengths as necessary
147
+ for j in range(len(carray)*8, ti.tracklen):
148
+ ticks.append(1000)
149
+ # Clip the timing info, if necessary.
150
+ ticks = ticks = ticks[:ti.tracklen]
151
+
152
+ ti.bits = bits
153
+ ti.ticks = ticks
154
+
155
+ # We don't really have access to the bitrate. It depends on RPM.
156
+ # So we assume a rotation rate of 300 RPM (5 rev/sec).
157
+ ti.rpm = 300
158
+
159
+
160
+ class CAPS(Image):
161
+
162
+ iid: int
163
+ pi: CapsImageInfo
164
+ imagetype: str
165
+
166
+ read_only = True
167
+
168
+ def __init__(self, name: str, _fmt) -> None:
169
+ self.filename = name
170
+ self.lib = get_libcaps()
171
+
172
+ def __del__(self) -> None:
173
+ try:
174
+ self.lib.CAPSUnlockAllTracks(self.iid)
175
+ self.lib.CAPSUnlockImage(self.iid)
176
+ self.lib.CAPSRemImage(self.iid)
177
+ del(self.iid)
178
+ except AttributeError:
179
+ pass
180
+
181
+ def __str__(self) -> str:
182
+ raise NotImplementedError
183
+
184
+ def get_track(self, cyl: int, head: int) -> Optional[MasterTrack]:
185
+ raise NotImplementedError
186
+
187
+ @classmethod
188
+ def from_file(cls, name: str, _fmt, opts: OptDict) -> Image:
189
+
190
+ caps = cls(name, _fmt)
191
+ caps.apply_r_opts(opts)
192
+ errprefix = f'CAPS: {cls.imagetype}'
193
+
194
+ caps.iid = caps.lib.CAPSAddImage()
195
+ error.check(caps.iid >= 0,
196
+ f"{errprefix}: Could not create image container")
197
+ cname = ct.c_char_p(name.encode())
198
+ res = caps.lib.CAPSLockImage(caps.iid, cname)
199
+ error.check(res == 0,
200
+ f"{errprefix}: Could not open image '{name}'")
201
+ res = caps.lib.CAPSLoadImage(caps.iid, DI_LOCK.def_flags)
202
+ error.check(res == 0,
203
+ f"{errprefix}: Could not load image '%s'" % name)
204
+ caps.pi = CapsImageInfo()
205
+ res = caps.lib.CAPSGetImageInfo(ct.byref(caps.pi), caps.iid)
206
+ error.check(res == 0,
207
+ f"{errprefix}: Could not get info for image '{name}'")
208
+ print(caps)
209
+
210
+ return caps
211
+
212
+
213
+ class CTRaw(CAPS):
214
+
215
+ imagetype = 'CTRaw'
216
+
217
+ def __str__(self) -> str:
218
+ pi = self.pi
219
+ s = "CTRaw Image File:"
220
+ s += ("\n Cyls: %d-%d Heads: %d-%d"
221
+ % (pi.mincylinder, pi.maxcylinder, pi.minhead, pi.maxhead))
222
+ return s
223
+
224
+ def get_track(self, cyl: int, head: int) -> Optional[MasterTrack]:
225
+
226
+ try:
227
+ ti = CAPSTrackInfo(self, cyl, head)
228
+ except CAPSTrackInfo.NoTrack:
229
+ return None
230
+
231
+ # CTRaw dumps get bogus speed info from the CAPS library.
232
+ # Assume they are uniform density.
233
+ bit_ticks = None # ti.ticks
234
+
235
+ return MasterTrack(
236
+ bits = ti.bits,
237
+ time_per_rev = 60/ti.rpm,
238
+ bit_ticks = bit_ticks)
239
+
240
+
241
+ class IPFTrack(MasterTrack):
242
+
243
+ verify_revs: float = 2
244
+ tolerance = 100
245
+
246
+ sectors: RangeList
247
+
248
+ @staticmethod
249
+ def strong_data(sector: RangeList, weak: RangeList) -> Generator:
250
+ """Return list of sector data areas excluding weak sections."""
251
+ def range_next(i):
252
+ s,l = next(i)
253
+ return s, s+l
254
+ weak_tol = 16 # Skip this number of bits after a weak area
255
+ weak_iter = it.chain(weak, [(1<<30,1)])
256
+ ws,we = -1,-1
257
+ sector_iter = iter(sector)
258
+ s,e = range_next(sector_iter)
259
+ try:
260
+ while True:
261
+ while we <= s:
262
+ ws,we = range_next(weak_iter)
263
+ we += weak_tol
264
+ if ws < e:
265
+ if s < ws:
266
+ yield (s,ws-s)
267
+ s = we
268
+ else:
269
+ yield (s,e-s)
270
+ s = e
271
+ if s >= e:
272
+ s,e = range_next(sector_iter)
273
+ except StopIteration:
274
+ pass
275
+
276
+ def verify_track(self, flux: Flux) -> bool:
277
+ flux.cue_at_index()
278
+ raw = PLLTrack(clock = self.time_per_rev/len(self.bits), data = flux)
279
+ raw_bits, _ = raw.get_all_data()
280
+ for s,l in IPFTrack.strong_data(self.sectors, self.weak):
281
+ sector = self.bits[s:s+l]
282
+ # Search within an area +/- the pre-defined # bitcells tolerance
283
+ raw_area = raw_bits[max(self.splice + s - self.tolerance, 0)
284
+ : self.splice + s + l + self.tolerance]
285
+ # All we care about is at least one match (this is a bit fuzzy)
286
+ if next(raw_area.search(sector), None) is None:
287
+ return False
288
+ return True
289
+
290
+
291
+ class IPF(CAPS):
292
+
293
+ imagetype = 'IPF'
294
+
295
+ def __str__(self) -> str:
296
+ pi = self.pi
297
+ s = "IPF Image File:"
298
+ if pi.release == 0x843265bb: # disk-utilities:IPF_ID
299
+ s += "\n SPS ID: None (https://github.com/keirf/disk-utilities)"
300
+ else:
301
+ s += "\n SPS ID: %04d (rev %d)" % (pi.release, pi.revision)
302
+ s += "\n Platform: "
303
+ nr_platforms = 0
304
+ for p in pi.platform:
305
+ if p == 0 and nr_platforms != 0:
306
+ break
307
+ if nr_platforms > 0:
308
+ s += ", "
309
+ s += pi.platform_name[p]
310
+ nr_platforms += 1
311
+ s += ("\n Created: %d/%d/%d %02d:%02d:%02d"
312
+ % (pi.crdt.year, pi.crdt.month, pi.crdt.day,
313
+ pi.crdt.hour, pi.crdt.min, pi.crdt.sec))
314
+ s += ("\n Cyls: %d-%d Heads: %d-%d"
315
+ % (pi.mincylinder, pi.maxcylinder, pi.minhead, pi.maxhead))
316
+ return s
317
+
318
+ def get_track(self, cyl: int, head: int) -> Optional[MasterTrack]:
319
+
320
+ try:
321
+ ti = CAPSTrackInfo(self, cyl, head)
322
+ except CAPSTrackInfo.NoTrack:
323
+ return None
324
+
325
+ data = []
326
+ for i in range(ti.sectorcnt):
327
+ si = CapsSectorInfo()
328
+ res = self.lib.CAPSGetInfo(ct.byref(si), self.iid,
329
+ cyl, head, 1, i)
330
+ error.check(res == 0, "Couldn't get sector info")
331
+ # Adjust the range start to be splice- rather than index-relative
332
+ data.append((si.datastart % ti.tracklen, si.datasize))
333
+
334
+ weak = []
335
+ for i in range(ti.weakcnt):
336
+ wi = CapsDataInfo()
337
+ res = self.lib.CAPSGetInfo(ct.byref(wi), self.iid,
338
+ cyl, head, 2, i)
339
+ error.check(res == 0, "Couldn't get weak data info")
340
+ # Adjust the range start to be splice- rather than index-relative
341
+ weak.append((wi.start % ti.tracklen, wi.size))
342
+
343
+ if ti.overlap < 0 and weak:
344
+ # Splice halfway through the longest weak area.
345
+ longest_weak = 0
346
+ for i,(s,n) in enumerate(weak):
347
+ if n > weak[longest_weak][1]:
348
+ longest_weak = i
349
+ s,n = weak[longest_weak]
350
+ if n > 200:
351
+ ti.overlap = (s + n//2) % ti.tracklen
352
+
353
+ if ti.overlap < 0 and data:
354
+ # Splice halfway through the longest gap area.
355
+ data.sort()
356
+ gap = []
357
+ for i,(s,n) in enumerate(data):
358
+ gap.append((data[(i+1)%len(data)][0] - s - n) % ti.tracklen)
359
+ i = gap.index(max(gap))
360
+ s,n = data[i]
361
+ ti.overlap = (s + n + gap[i] // 2) % ti.tracklen
362
+
363
+ if ti.overlap < 0:
364
+ # No sector or weak information. Splice at the index.
365
+ ti.overlap = 0
366
+
367
+ if ti.overlap:
368
+
369
+ # Adjust range starts to be splice- rather than index-relative
370
+ f = lambda x: ((x[0] - ti.overlap) % ti.tracklen, x[1])
371
+ data = list(map(f, data))
372
+ weak = list(map(f, weak))
373
+
374
+ # Rotate the track to start at the splice rather than the index.
375
+ ti.bits = ti.bits[ti.overlap:] + ti.bits[:ti.overlap]
376
+ if ti.ticks:
377
+ ti.ticks = ti.ticks[ti.overlap:] + ti.ticks[:ti.overlap]
378
+
379
+ # Sort ranges by start, and clip at splice point.
380
+ def clip_and_sort_ranges(r):
381
+ res = []
382
+ for i,(s,n) in enumerate(r):
383
+ if s + n > ti.tracklen:
384
+ res.append((s,ti.tracklen-s))
385
+ res.append((0, s+n-ti.tracklen))
386
+ else:
387
+ res.append((s,n))
388
+ res.sort()
389
+ return res
390
+ data = clip_and_sort_ranges(data)
391
+ weak = clip_and_sort_ranges(weak)
392
+
393
+ track = IPFTrack(
394
+ bits = ti.bits,
395
+ time_per_rev = 60/ti.rpm,
396
+ bit_ticks = ti.ticks,
397
+ splice = ti.overlap,
398
+ weak = weak
399
+ )
400
+ track.verify = track
401
+ track.sectors = data
402
+ return track
403
+
404
+
405
+ # Open and initialise the CAPS library.
406
+ def open_libcaps():
407
+
408
+ # Get the OS-dependent list of valid CAPS library names.
409
+ _names = []
410
+ if platform.system() == "Linux":
411
+ _names = [ "libcapsimage.so.5", "libcapsimage.so.5.1",
412
+ "libcapsimage.so.4", "libcapsimage.so.4.2",
413
+ "libcapsimage.so" ]
414
+ elif platform.system() == "Darwin":
415
+ _names = [ "CAPSImage.framework/CAPSImage",
416
+ "CAPSImg.framework/CAPSImg" ]
417
+ elif platform.system() == "Windows":
418
+ _names = [ "CAPSImg_x64.dll", "CAPSImg.dll" ]
419
+
420
+ # Get the absolute path to the root Greaseweazle folder.
421
+ path = os.path.dirname(os.path.abspath(__file__))
422
+ for _ in range(3):
423
+ path = os.path.join(path, os.pardir)
424
+ path = os.path.normpath(path)
425
+
426
+ # Create a search list of both relative and absolute library names.
427
+ names = []
428
+ for name in _names:
429
+ names.append(name)
430
+ names.append(os.path.join(path, name))
431
+ if platform.system() == "Darwin":
432
+ names.append(os.path.join('/Library/Frameworks', name))
433
+
434
+ # Walk the search list, trying to open the CAPS library.
435
+ for name in names:
436
+ try:
437
+ lib = ct.cdll.LoadLibrary(name)
438
+ break
439
+ except:
440
+ pass
441
+
442
+ error.check("lib" in locals(), """\
443
+ Could not find SPS/CAPS library
444
+ For installation instructions please read the wiki:
445
+ <https://github.com/keirf/greaseweazle/wiki/IPF-Images>""")
446
+
447
+ # We have opened the library. Now initialise it.
448
+ res = lib.CAPSInit()
449
+ error.check(res == 0, "Failure initialising CAPS/SPS library '%s'" % name)
450
+
451
+ return lib
452
+
453
+
454
+ # Get a reference to the CAPS library. Open it if necessary.
455
+ def get_libcaps():
456
+ global libcaps
457
+ if not 'libcaps' in globals():
458
+ libcaps = open_libcaps()
459
+ return libcaps
460
+
461
+
462
+ # Local variables:
463
+ # python-indent: 4
464
+ # End:
@@ -0,0 +1,51 @@
1
+ # greaseweazle/image/d64.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 Optional
9
+
10
+ import struct
11
+
12
+ from diskstack._vendor.greaseweazle import error
13
+ from diskstack._vendor.greaseweazle.image.image import Image
14
+ from diskstack._vendor.greaseweazle.image.img import IMG
15
+ from diskstack._vendor.greaseweazle.codec import codec
16
+ from diskstack._vendor.greaseweazle.codec.commodore import c64_gcr
17
+
18
+ class D64(IMG):
19
+ default_format = 'commodore.1541'
20
+ min_cyls: Optional[int] = 35
21
+
22
+ def get_disk_id(self):
23
+ t = self.get_track(17, 0) # BAM, track 18 (counting from 1), sector 0
24
+ if t is None:
25
+ return None
26
+ dat = t.get_img_track()
27
+ if len(dat) < 164:
28
+ return None
29
+ # NB disk_id byte order in BAM is reverse of sector headers
30
+ disk_id, = struct.unpack('<H', dat[162:164])
31
+ return disk_id
32
+
33
+ def from_bytes(self, dat: bytes) -> None:
34
+ super().from_bytes(dat)
35
+ disk_id = self.get_disk_id()
36
+ for _, t in self.to_track.items():
37
+ error.check(isinstance(t, c64_gcr.C64GCR),
38
+ f'{self.__class__.__name__}: '
39
+ f'Only {self.default_format} format is supported')
40
+ assert isinstance(t, c64_gcr.C64GCR) # mypy
41
+ if disk_id is not None:
42
+ t.set_disk_id(disk_id)
43
+
44
+ class D71(D64):
45
+ default_format = 'commodore.1571'
46
+ sequential = True
47
+ min_cyls = None
48
+
49
+ # Local variables:
50
+ # python-indent: 4
51
+ # End: