Initial support for multiple types of disk images

* added KBoot image detection
* created base class for disk images
This commit is contained in:
Rob McMullen 2016-02-07 22:22:21 -08:00
parent 4093077e3c
commit 5ed7afc9c3

View File

@ -14,6 +14,9 @@ class AtrError(RuntimeError):
class InvalidAtrHeader(AtrError): class InvalidAtrHeader(AtrError):
pass pass
class InvalidDiskImage(AtrError):
pass
class LastDirent(AtrError): class LastDirent(AtrError):
pass pass
@ -81,6 +84,9 @@ class AtrHeader(object):
self.sector_size = 256 self.sector_size = 256
self.initial_sector_size = 128 self.initial_sector_size = 128
self.num_initial_sectors = 3 self.num_initial_sectors = 3
else:
self.image_size = size
self.sector_size = 128
initial_bytes = self.initial_sector_size * self.num_initial_sectors initial_bytes = self.initial_sector_size * self.num_initial_sectors
self.max_sectors = ((self.image_size - initial_bytes) / self.sector_size) + self.num_initial_sectors self.max_sectors = ((self.image_size - initial_bytes) / self.sector_size) + self.num_initial_sectors
@ -413,14 +419,11 @@ class AtrFileSegment(ObjSegment):
s += " " + self.error s += " " + self.error
return s return s
class AtrDiskImage(object):
class DiskImageBase(object):
def __init__(self, bytes): def __init__(self, bytes):
self.bytes = to_numpy(bytes) self.bytes = to_numpy(bytes)
self.header = None self.header = None
self.first_vtoc = 360
self.num_vtoc = 1
self.vtoc2 = 0
self.first_data_after_vtoc = 369
self.total_sectors = 0 self.total_sectors = 0
self.unused_sectors = 0 self.unused_sectors = 0
self.files = [] self.files = []
@ -428,27 +431,10 @@ class AtrDiskImage(object):
self.all_sane = True self.all_sane = True
self.setup() self.setup()
def __str__(self):
if self.all_sane:
return "%s %d usable sectors (%d free), %d files" % (self.header, self.total_sectors, self.unused_sectors, len(self.files))
else:
return "%s bad directory entries; possible boot disk? Use -f option to try to extract anyway" % self.header
def dir(self):
lines = []
lines.append(str(self))
for dirent in self.files:
if dirent.in_use:
lines.append(str(dirent))
return "\n".join(lines)
def setup(self): def setup(self):
self.size = len(self.bytes) self.size = np.alen(self.bytes)
self.read_atr_header() self.read_atr_header()
self.check_size() self.check_size()
self.get_vtoc()
self.get_directory()
def read_atr_header(self): def read_atr_header(self):
bytes = self.bytes[0:16] bytes = self.bytes[0:16]
@ -480,6 +466,47 @@ class AtrDiskImage(object):
size += more size += more
return self.bytes[pos:pos + size] return self.bytes[pos:pos + size]
def get_contiguous_sectors(self, sector, num):
start = 0
count = 0
for index in range(sector, sector + num):
pos, size = self.header.get_pos(index)
if start == 0:
start = pos
count += size
return start, count
class AtariDosDiskImage(DiskImageBase):
def __init__(self, bytes):
self.first_vtoc = 360
self.num_vtoc = 1
self.vtoc2 = 0
self.first_data_after_vtoc = 369
DiskImageBase.__init__(self, bytes)
def __str__(self):
if self.all_sane:
return "%s Atari DOS Format: %d usable sectors (%d free), %d files" % (self.header, self.total_sectors, self.unused_sectors, len(self.files))
else:
return "%s bad directory entries; possible boot disk? Use -f option to try to extract anyway" % self.header
def dir(self):
lines = []
lines.append(str(self))
for dirent in self.files:
if dirent.in_use:
lines.append(str(dirent))
return "\n".join(lines)
def setup(self):
self.size = np.alen(self.bytes)
self.read_atr_header()
self.check_size()
self.get_vtoc()
self.get_directory()
vtoc_type = np.dtype([ vtoc_type = np.dtype([
('code', 'u1'), ('code', 'u1'),
('total','<u2'), ('total','<u2'),
@ -533,16 +560,6 @@ class AtrDiskImage(object):
return self.get_file(dirent) return self.get_file(dirent)
return "" return ""
def get_contiguous_sectors(self, sector, num):
start = 0
count = 0
for index in range(sector, sector + num):
pos, size = self.header.get_pos(index)
if start == 0:
start = pos
count += size
return start, count
def get_obj_segment(self, metadata_start, data_start, start_addr, end_addr, data, name): def get_obj_segment(self, metadata_start, data_start, start_addr, end_addr, data, name):
"""Subclass use: override this method to create a custom segment. """Subclass use: override this method to create a custom segment.
@ -643,18 +660,35 @@ class AtrDiskImage(object):
self.segments.extend(self.get_directory_segments()) self.segments.extend(self.get_directory_segments())
self.segments.extend(self.get_file_segments()) self.segments.extend(self.get_file_segments())
# for dirent in self.atr.files:
# try: class KBootImage(DiskImageBase):
# bytes = self.get_file(dirent) def __init__(self, bytes):
# error = None self.exe_start = 0
# except atrcopy.FileNumberMismatchError164: self.exe_size = 0
# bytes = None DiskImageBase.__init__(self, bytes)
# error = "Error 164"
# except atrcopy.ByteNotInFile166: def __str__(self):
# bytes = None return "%s KBoot Format: %d byte executable" % (self.header, self.exe_size)
# error = "Invalid sector"
# a = AtrFileSegment(dirent, bytes, error) def check_size(self):
# self.segments.append(AtrSegment(dirent)) self.header.check_size(self.size)
start, size = self.header.get_pos(4)
i = self.header.atr_header_offset + 9
count = self.bytes[i] + 256 * self.bytes[i+1] + 256 * 256 *self.bytes[i + 2]
if start + count > self.size or start + count < self.size - 128:
raise InvalidDiskImage("Doesn't seem to be KBoot header")
self.exe_size = count
self.exe_start = start
def get_file_segment(self):
return ObjSegment(0, 0, 0, self.exe_start, self.bytes[self.exe_start:self.exe_start + self.exe_size], name="KBoot Executable")
def parse_segments(self):
if self.header.image_size > 0:
self.segments.append(ObjSegment(0, 0, 0, self.header.atr_header_offset, self.bytes[0:self.header.atr_header_offset], name="%s Header" % self.header.file_format))
self.segments.append(RawSectorsSegment(1, self.header.max_sectors, self.header.image_size, self.bytes[self.header.atr_header_offset:], name="Raw disk sectors"))
self.segments.append(self.get_file_segment())
def to_numpy(value): def to_numpy(value):
if type(value) is np.ndarray: if type(value) is np.ndarray:
@ -710,9 +744,29 @@ if __name__ == "__main__":
with open(filename, "rb") as fh: with open(filename, "rb") as fh:
data = fh.read() data = fh.read()
try: try:
atr = AtrDiskImage(data) data = to_numpy(data)
atr = None
try:
header = AtrHeader(data[0:16])
for format in [KBootImage, AtariDosDiskImage]:
print "trying", format.__name__
try:
atr = format(data)
print "%s: %s" % (filename, atr) print "%s: %s" % (filename, atr)
break
except InvalidDiskImage:
pass
except InvalidAtrHeader:
for format in [AtariDosDiskImage]:
try:
atr = format(data)
print "%s: %s" % (filename, atr)
break
except: except:
raise
#pass
except:
raise
print "%s: Doesn't look like a supported disk image" % filename print "%s: Doesn't look like a supported disk image" % filename
try: try:
xex = AtariDosFile(data) xex = AtariDosFile(data)