Skip to content

Commit e35f274

Browse files
jasonalexander-jakeirf
authored andcommitted
Add ability to read apridisk disk archive file format
1 parent 9627174 commit e35f274

2 files changed

Lines changed: 112 additions & 1 deletion

File tree

src/greaseweazle/image/apridisk.py

Lines changed: 107 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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 greaseweazle import error
11+
from greaseweazle.codec import codec
12+
from 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:

src/greaseweazle/image/dsk.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,18 +8,22 @@
88
from greaseweazle.image.image import Image, OptDict
99
from greaseweazle.image.img import IMG
1010
from greaseweazle.image.edsk import EDSK
11+
from greaseweazle.image.apridisk import Apridisk
1112

1213
class DSK(IMG):
1314

1415
@classmethod
1516
def from_file(cls, name: str, fmt, opts: OptDict) -> Image:
1617

1718
with open(name, "rb") as f:
18-
sig = f.read(16)
19+
sig = f.read(24)
1920

2021
if sig[:8] == b'MV - CPC' or sig[:16] == b'EXTENDED CPC DSK':
2122
return EDSK.from_file(name, fmt, opts)
2223

24+
if sig[:24] == b"ACT Apricot disk image\032\004":
25+
return Apridisk.from_file(name, fmt, opts)
26+
2327
return IMG.from_file(name, fmt, opts)
2428

2529
# Local variables:

0 commit comments

Comments
 (0)