So I'm creating a tool to read variable length records into memory, and access them as a struct.
Reading this information from disk to a structure seems straight forward. I'm using a helper class from the O'Reilly Python CookBook
And this all works as expected.
Within an existing Struct
Using:
dskHead = DSKHeader(file.read(256))
Reads the data in and I can access elements in the structure by dskHead.numberOfTracks and that's all great.
Further on I read in what will be the sector information which will contain variable length records within the 256 data block.
class TrackInformationBlock(Structure):
_fields_ = [
('<12s','header'),
('<4s','unused'),
('b','TrackNumber'),
('b','TrackSide'),
('h','unused2'),
('b','sectorSize'),
('b','numberOfSectors'),
('b','gap3'),
('b','filler'),
('<232s','sectorTable')
]
Again, all fine and working as expected.
the sectorTable is already in memory at this point and that can be broken down by a further struct.
class SectorInformationBlock(Structure):
_fields_ = [
('b','Track'),
('b','Side'),
('b','SectorID'),
('b','SectorSize'),
('b','FDC1'),
('b','FDC2'),
('h','notused')
]
determined by the number of sectors.
Since the SectorTable is already in memory (ByteArray),
How can I associate the SectorInformationBlock via iteration over the 232 bytes and be able to refer to each by fieldname?
as opposed to Track.sectorTable[0:8] , Track.sectorTable[9:16] etc?
Only examples I can find is when reading data from disk, not after data is already in memory?
Hope this makes sense, though if you need full context the full project is https://github.com/muckypaws/AmstradDSKExplorer
I prefer consistency and although my solution works, it doesn't quite have that feel of clean Python code.
I am still very much learning this, as I come from an Assembler language background, where I would just simply refer to memory addresses and pointers.
Thank you
Code snippets below for your reference. There are 9 instances of the 8 Byte Sector Information Block. So I would prefer to access on key, i.e. track.sectorTable.sector["C7"]
track.sectorTable.hex() '0000c102000000020000c602000000020000c202000000020000c702000000020000c302000000020000c802000000020000c402000000020000c902000000020000c5020000000200000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000'
track.sectorTable b'\x00\x00\xc1\x02\x00\x00\x00\x02\x00\x00\xc6\x02\x00\x00\x00\x02\x00\x00\xc2\x02\x00\x00\x00\x02\x00\x00\xc7\x02\x00\x00\x00\x02\x00\x00\xc3\x02\x00\x00\x00\x02\x00\x00\xc8\x02\x00\x00\x00\x02\x00\x00\xc4\x02\x00\x00\x00\x02\x00\x00\xc9\x02\x00\x00\x00\x02\x00\x00\xc5\x02\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
#
# Helper Class for creating easy structs.
# Nabbed from the O'Reilly Book
# Python Cookbook 3rd Edition - Recipes for Mastering Python 3
# ISBN 978-1-449-34037-7
#
class SizedRecord:
def __init__(self, bytedata):
self._buffer = memoryview(bytedata)
@classmethod
def from_file(cls, f, size_fmt, includes_size=True):
sz_nbytes = struct.calcsize(size_fmt)
sz_bytes = f.read(sz_nbytes)
sz, = struct.unpack(size_fmt, sz_bytes)
buf = f.read(sz - includes_size * sz_nbytes)
return cls(buf)
def iter_as(self, code):
if isinstance(code, str):
s = struct.Struct(code)
for off in range(0, len(self._buffer), s.size):
yield s.unpack_from(self._buffer, off)
elif isinstance(code, StructureMeta):
size = code.struct_size
for off in range(0, len(self._buffer), size):
data = self._buffer[off:off+size]
yield code(data)
class StructField:
'''
Descriptor representing a simple structure field
'''
def __init__(self, format, offset):
self.format = format
self.offset = offset
def __get__(self, instance, cls):
if instance is None:
return self
else:
r = struct.unpack_from(self.format,
instance._buffer, self.offset)
return r[0] if len(r) == 1 else r
class NestedStruct:
'''
Descriptor representing a nested structure
'''
def __init__(self, name, struct_type, offset):
self.name = name
self.struct_type = struct_type
self.offset = offset
def __get__(self, instance, cls):
if instance is None:
return self
else:
data = instance._buffer[self.offset: self.offset+self.struct_type.struct_size]
result = self.struct_type(data)
# Save resulting structure back on instance to avoid
# further recomputation of this step setattr(instance, self.name, result)
return result
class StructureMeta(type):
'''
Metaclass that automatically creates StructField descriptors
'''
def __init__(self, clsname, bases, clsdict):
fields = getattr(self, '_fields_', [])
byte_order = ''
offset = 0
for format, fieldname in fields:
if isinstance(format, StructureMeta):
setattr(self,fieldname,
NestedStruct(fieldname, format, offset))
offset += format.struct_size
else:
if format.startswith(('<','>','!','@')):
byte_order = format[0]
format = format[1:]
format = byte_order + format
setattr(self, fieldname, StructField(format, offset))
offset += struct.calcsize(format)
setattr(self, 'struct_size', offset)
class Structure(metaclass=StructureMeta):
def __init__(self, bytedata):
self._buffer = memoryview(bytedata)
#
# Disk Header Structure
#
class DSKHeader(Structure):
_fields_ = [
('<34s','header'),
('<14s','creator'),
('b','numberOfTracks'),
('b','numberOfSides'),
('h','oldTrackSize'),
('<204s','trackSizeTable')
]
#
# Define the Sector Information Block Structure
#
class SectorInformationBlock(Structure):
_fields_ = [
('b','Track'),
('b','Side'),
('b','SectorID'),
('b','SectorSize'),
('b','FDC1'),
('b','FDC2'),
('h','notused')
]
#
# Define the Track Information Block Structure
#
class TrackInformationBlock(Structure):
_fields_ = [
('<12s','header'),
('<4s','unused'),
('b','TrackNumber'),
('b','TrackSide'),
('h','unused2'),
('b','sectorSize'),
('b','numberOfSectors'),
('b','gap3'),
('b','filler'),
#(SectorInformationBlock,'sectorTable[29]')
('<232s','sectorTable')
]
Dr google, my Python books and ... I'm just not experienced a Python person to solve this...