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
This commit is contained in:
kris 2017-04-20 23:04:24 +01:00
parent 8148053965
commit f3bde766bf
4 changed files with 104 additions and 30 deletions

View File

@ -1,3 +1,5 @@
import anomaly
import container
import bitstring
TOKENS = {
@ -110,15 +112,18 @@ TOKENS = {
0xEA: 'MID$'
}
# TODO: report anomaly if an unknown token is used
class AppleSoft(container.Container):
def __init__(self, filename, data):
super(AppleSoft, self).__init__()
class AppleSoft(object):
def __init__(self, data):
self.filename = filename
data = bitstring.ConstBitStream(data)
# TODO: assert length is met
self.length = data.read('uintle:16')
self.lines = {}
self.lines = []
self.program = {}
last_line_number = -1
last_memory = 0x801
while data:
@ -132,25 +137,43 @@ class AppleSoft(object):
token = data.read('uint:8')
bytes_read += 1
if token == 0:
self.lines[line_number] = ''.join(line)
self.lines.append(line_number)
self.program[line_number] = ''.join(line)
break
if token in TOKENS:
line.append(' ' + TOKENS[token] + ' ')
if token >= 0x80:
try:
line.append(' ' + TOKENS[token] + ' ')
except KeyError:
self.anomalies.append(anomaly.Anomaly(
self, anomaly.CORRUPTION, 'Line number %d contains unexpected token: %02X' % (
line_number, token)
)
)
else:
line.append(chr(token))
if last_memory + bytes_read != next_memory:
print "%x + %x == %x != %x (gap %d)" % (last_memory, bytes_read, last_memory + bytes_read, next_memory, next_memory - last_memory - bytes_read)
self.anomalies.append(anomaly.Anomaly(
self, anomaly.UNUSUAL, "%x + %x == %x != %x (gap %d)" % (
last_memory, bytes_read, last_memory + bytes_read, next_memory,
next_memory - last_memory - bytes_read)
)
)
if line_number <= last_line_number:
print "%d <= %d: %s" % (line_number, last_line_number, ''.join(line))
self.anomalies.append(anomaly.Anomaly(
self, anomaly.UNUSUAL, "%d <= %d: %s" % (
line_number, last_line_number, ''.join(line))
)
)
print "%d %s" % (line_number, ''.join(line))
last_line_number = line_number
last_memory = next_memory
def List(self):
return '\n'.join('%s %s' % (num, self.program[num]) for num in self.lines)
def __str__(self):
return '\n'.join('%s %s' % (num, line) for (num, line) in sorted(self.lines.items()))
return 'AppleSoft(%s)' % self.filename

View File

@ -0,0 +1,20 @@
class Container(object):
"""Generic container type, every structure on the disk extends from this."""
def __init__(self):
self.anomalies = []
self.parent = None
self.children = []
def AddChild(self, child):
assert child.parent is None, "%s already has parent %s" % (child, child.parent)
self.children.append(child)
child.parent = self
def Recurse(self, callback):
"""Depth-first recursive traversal of children."""
for child in self.children:
callback(child)
child.Recurse(callback)

View File

@ -1,3 +1,6 @@
import anomaly
import container
import bitstring
import hashlib
import zlib
@ -12,16 +15,20 @@ TRACK_SIZE = SECTORS_PER_TRACK * SECTOR_SIZE
class IOError(Exception):
pass
class Disk(object):
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)
@ -47,6 +54,8 @@ class Disk(object):
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):
@ -56,11 +65,12 @@ class Disk(object):
raise IOError("Track $%02x sector $%02x out of bounds" % (track, sector))
class Sector(object):
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
@ -75,6 +85,7 @@ class Sector(object):
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

View File

@ -1,8 +1,11 @@
import anomaly
import applesoft
import bitstring
import container
import disk as disklib
import utils
import bitstring
class FileType(object):
def __init__(self, short_type, long_type, parser=None):
self.short_type = short_type
@ -56,7 +59,13 @@ class VTOCSector(disklib.Sector):
if free:
old_sector = self.disk.ReadSector(track, sector)
# check first this is an unclaimed sector
assert type(old_sector) == disklib.Sector
if type(old_sector) != disklib.Sector:
self.anomalies.append(
anomaly.Anomaly(
self, anomaly.CORRUPTION, 'VTOC claims used sector is free: %s' % old_sector
)
)
FreeSector.fromSector(old_sector)
# TODO: also handle sectors that are claimed to be used but don't end up getting referenced by anything
@ -232,17 +241,15 @@ class Dos33Disk(disklib.Disk):
continue
contents.append(fds.data)
return File(entry, contents)
newfile = File(entry, contents)
self.AddChild(newfile)
return newfile
def __str__(self):
catalog = ['DISK VOLUME %d\n' % self.volume]
for filename in self.catalog:
entry = self.files[filename]
try:
file_type = FILE_TYPES[entry.file_type].short_type
except KeyError:
print "%s has unknown file type %02x" % (entry.FileName(), entry.file_type)
file_type = '?'
for filename in self.filenames:
entry = self.catalog[filename]
file_type = entry.file_type.short_type
catalog.append(
'%s%s %03d %s' % (
'*' if entry.locked else ' ',
@ -253,10 +260,13 @@ class Dos33Disk(disklib.Disk):
return '\n'.join(catalog)
class CatalogEntry(object):
class CatalogEntry(container.Container):
def __init__(self, track, sector, file_type, file_name, length):
super(CatalogEntry, self).__init__()
self.track = track
self.sector = sector
# TODO: add anomaly for unknown file type
self.file_type = FILE_TYPES[file_type & 0x7f]
self.locked = bool(file_type & 0x80)
self.file_name = file_name
@ -273,16 +283,26 @@ class CatalogEntry(object):
return "Track $%02x Sector $%02x Type %s Name: %s Length: %d" % (self.track, self.sector, type_string, self.FileName(), self.length)
class File(object):
class File(container.Container):
def __init__(self, catalog_entry, contents):
super(File, self).__init__()
self.catalog_entry = catalog_entry
self.contents = contents
self.parsed_contents = None
parser = catalog_entry.file_type.parser
if parser:
try:
self.parsed_contents = parser(contents)
except Exception:
print "Failed to parse file %s" % self.catalog_entry
print utils.HexDump(contents.tobytes())
self.parsed_contents = parser(catalog_entry.FileName(), contents)
self.AddChild(self.parsed_contents)
except Exception, e:
self.anomalies.append(
anomaly.Anomaly(
self, anomaly.CORRUPTION, 'Failed to parse file %s: %s' % (self.catalog_entry, e)
)
)
def __str__(self):
return 'File(%s)' % self.catalog_entry.FileName()