mirror of
https://github.com/KrisKennaway/pyapple2disk.git
synced 2024-06-10 00:29:56 +00:00
f3bde766bf
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
126 lines
4.2 KiB
Python
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)
|