|
| 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: |
0 commit comments