pyapple2disk/src/apple2disk/disk.py
kris f3bde766bf Add a container.Container() type that carries a list of Anomaly()
objects associated to the container, and builds a tree of child
containers with support for depth-first recursion

Convert all of the disk container classes to use Container() and
register themselves with this tree.  This constructs a hierarchy of
container objects rooted in Disk that describe the structures found on
the disk image.

Convert most of the print statements and some of the assertions to
register Anomaly records instead

Fix/improve some of the __str__ representations
2017-04-20 23:04:24 +01:00

126 lines
4.2 KiB
Python

import anomaly
import container
import bitstring
import hashlib
import zlib
SECTOR_SIZE = 256
SECTORS_PER_TRACK = 16
# TODO: support larger disks
TRACKS_PER_DISK = 35
TRACK_SIZE = SECTORS_PER_TRACK * SECTOR_SIZE
class IOError(Exception):
pass
class Disk(container.Container):
def __init__(self, name, data):
super(Disk, self).__init__()
self.name = name
self.data = data
# TODO: support larger disk sizes
assert len(data) == 140 * 1024
self.hash = hashlib.sha1(data).hexdigest()
self.sectors = {}
# Pre-load all sectors into map
for (track, sector) in self.EnumerateSectors():
self._ReadSector(track, sector)
# Assign ownership of T0, S0 to RWTS
self.rwts = RWTS.fromSector(self.ReadSector(0, 0))
@classmethod
def Taste(cls, disk):
# TODO: return a defined exception here
return cls(disk.name, disk.data)
def SetSectorOwner(self, track, sector, owner):
self.sectors[(track, sector)] = owner
def EnumerateSectors(self):
for track in xrange(TRACKS_PER_DISK):
for sector in xrange(SECTORS_PER_TRACK):
yield (track, sector)
def _ReadSector(self, track, sector):
offset = track * TRACK_SIZE + sector * SECTOR_SIZE
if sector >= SECTORS_PER_TRACK or offset > len(self.data):
raise IOError("Track $%02x sector $%02x out of bounds" % (track, sector))
data = bitstring.BitString(self.data[offset:offset + SECTOR_SIZE])
# This calls SetSectorOwner to register in self.sectors
return Sector(self, track, sector, data)
def ReadSector(self, track, sector):
try:
return self.sectors[(track, sector)]
except KeyError:
raise IOError("Track $%02x sector $%02x out of bounds" % (track, sector))
class Sector(container.Container):
# TODO: other types will include: VTOC, Catalog, File metadata, File content, Deleted file, Free space
TYPE = 'Unknown sector'
def __init__(self, disk, track, sector, data):
super(Sector, self).__init__()
# Reference back to parent disk
self.disk = disk
self.track = track
self.sector = sector
self.data = data
self.hash = hashlib.sha1(data.tobytes()).hexdigest()
# Estimate entropy of disk sector
compressed_data = zlib.compress(data.tobytes())
self.compress_ratio = len(compressed_data) * 100 / len(data.tobytes())
disk.SetSectorOwner(track, sector, self)
disk.AddChild(self)
# TODO: if all callers are using disk.ReadSector(track, sector) to get the sector then do that here
@classmethod
def fromSector(cls, sector, *args, **kwargs):
"""Create and register a new Sector from an existing Sector object."""
# TODO: don't recompute hash and entropy
return cls(sector.disk, sector.track, sector.sector, sector.data, *args, **kwargs)
# TOOD: move RWTS ones into RWTS() class?
KNOWN_HASHES = {
'b376885ac8452b6cbf9ced81b1080bfd570d9b91': 'Zero sector',
'90e6b1a0689974743cb92ca0b833ff1e683f4a73': 'RWTS (DOS 3.3 August 1980)',
'7ab36247fdf62e87f98d2964dd74d6572d17fff0': 'RWTS (DOS 3.3 January 1983)',
'16e4c17a85eb321bae784ab716975ddeef6da2c6': 'RWTS (DOS 3.3 System Master)',
'822c7450afa01f46bbc828d4d46e01bc08d73198': 'RWTS (ProntoDOS (1982))',
'30da15678e0d70e20ecf86bcb2de3fd3874dbd0d': 'RWTS (ProntoDOS (March 1983))',
'93d81a812d824d58dedec8f7787e9cfcc7a2d3b3': 'RWTS (Apple Pascal, Fortran)',
'adeb3be5c3d9487a76f1917d1c28104a1a6fc72f': 'RWTS (Faster DOS 3.3?)',
'4f4aff4e1eb8d806164544b64dc967abd76128a4': 'RWTS (ProDOS?)'
}
def HumanName(self):
try:
human_name = self.KNOWN_HASHES[self.hash]
except KeyError:
human_name = "Hash %s (Entropy: %d%%)" % (self.hash, self.compress_ratio)
return human_name
def __str__(self):
return "Track $%02x Sector $%02x: %s (%s)" % (self.track, self.sector, self.TYPE, self.HumanName())
class RWTS(Sector):
TYPE = "RWTS"
def __init__(self, disk, track, sector, data):
super(RWTS, self).__init__(disk, track, sector, data)