Added parsers and MIME type definitions from Omnivore

* restructured main script to use parsers, replacing the old logic
This commit is contained in:
Rob McMullen 2016-06-01 14:54:52 -07:00
parent ea9466cda7
commit f1038329ee
7 changed files with 134 additions and 61 deletions

View File

@ -13,6 +13,7 @@ from diskimages import AtrHeader, BootDiskImage, add_atr_header
from kboot import KBootImage, add_xexboot_header
from segments import SegmentData, SegmentSaver, DefaultSegment, EmptySegment, ObjSegment, RawSectorsSegment, user_bit_mask, match_bit_mask, comment_bit_mask, data_bit_mask, selected_bit_mask, diff_bit_mask, not_user_bit_mask, interleave_segments
from spartados import SpartaDosDiskImage
from parsers import SegmentParser, DefaultSegmentParser, guess_parser_for, known_segment_parsers, mime_parse_order
from utils import to_numpy
@ -69,53 +70,29 @@ def run():
for filename in options.files:
with open(filename, "rb") as fh:
if options.verbose:
print "Loading file %s" % filename
rawdata = SegmentData(fh.read())
data = rawdata.get_data()
image = None
if options.debug:
header = AtrHeader(data[0:16])
image = SpartaDosDiskImage(rawdata, filename)
else:
try:
data = to_numpy(data)
try:
header = AtrHeader(data[0:16])
for format in [KBootImage, SpartaDosDiskImage, AtariDosDiskImage]:
if options.verbose: print "trying", format.__name__
try:
image = format(rawdata, filename)
print "%s: %s" % (filename, image)
break
except InvalidDiskImage:
pass
except AtrError:
for format in [AtariDosDiskImage]:
try:
image = format(rawdata, filename)
print "%s: %s" % (filename, image)
break
except:
raise
#pass
except AtrError:
if options.verbose: print "%s: Doesn't look like a supported disk image" % filename
try:
image = AtariDosFile(rawdata)
print "%s:\n%s" % (filename, image)
except InvalidBinaryFile:
if options.verbose: print "%s: Doesn't look like an XEX either" % filename
parser = None
for mime in mime_parse_order:
if options.verbose:
print "Trying MIME type %s" % mime
parser = guess_parser_for(mime, rawdata)
if parser is None:
continue
if image is None:
image = BootDiskImage(rawdata, filename)
if options.segments:
image.parse_segments()
print "\n".join([str(a) for a in image.segments])
elif image.files or options.force:
for dirent in image.files:
try:
process(image, dirent, options)
except FileNumberMismatchError164:
print "Error 164: %s" % str(dirent)
except ByteNotInFile166:
print "Invalid sector for: %s" % str(dirent)
if options.verbose:
print "Found parser %s" % parser.menu_name
print "%s: %s" % (filename, parser.image)
if options.segments:
print "\n".join([str(a) for a in parser.segments])
elif parser.image.files or options.force:
for dirent in parser.image.files:
try:
process(parser.image, dirent, options)
except FileNumberMismatchError164:
print "Error 164: %s" % str(dirent)
except ByteNotInFile166:
print "Invalid sector for: %s" % str(dirent)
break
if parser is None:
print "%s: Unknown file type" % filename

View File

@ -149,7 +149,7 @@ class AtariDosFile(object):
self.rawdata = rawdata
self.size = len(rawdata)
self.segments = []
self.parse_segments()
self.files = []
def __str__(self):
return "\n".join(str(s) for s in self.segments) + "\n"

View File

@ -23,12 +23,12 @@ class AtrHeader(object):
self.crc = 0
self.unused = 0
self.flags = 0
self.atr_header_offset = 0
self.header_offset = 0
self.initial_sector_size = sector_size
self.num_initial_sectors = initial_sectors
self.max_sectors = 0
if create:
self.atr_header_offset = 16
self.header_offset = 16
self.check_size(0)
if bytes is None:
return
@ -42,7 +42,7 @@ class AtrHeader(object):
self.crc = int(values[4])
self.unused = int(values[5])
self.flags = int(values[6])
self.atr_header_offset = 16
self.header_offset = 16
else:
raise InvalidAtrHeader
@ -50,7 +50,7 @@ class AtrHeader(object):
return "%s Disk Image (size=%d (%dx%db), crc=%d flags=%d unused=%d)" % (self.file_format, self.image_size, self.max_sectors, self.sector_size, self.crc, self.flags, self.unused)
def __len__(self):
return self.atr_header_offset
return self.header_offset
def to_array(self):
raw = np.zeros([16], dtype=np.uint8)
@ -100,7 +100,7 @@ class AtrHeader(object):
else:
pos = self.num_initial_sectors * self.initial_sector_size + (sector - 1 - self.num_initial_sectors) * self.sector_size
size = self.sector_size
pos += self.atr_header_offset
pos += self.header_offset
return pos, size
@ -149,7 +149,7 @@ class DiskImageBase(object):
def setup(self):
self.size = np.alen(self.bytes)
self.read_atr_header()
self.read_header()
self.header.check_size(self.size - len(self.header))
self.check_size()
self.get_boot_sector_info()
@ -195,7 +195,7 @@ class DiskImageBase(object):
if not self.all_sane:
raise InvalidDiskImage("Invalid directory entries; may be boot disk")
def read_atr_header(self):
def read_header(self):
bytes = self.bytes[0:16]
try:
self.header = AtrHeader(bytes)
@ -256,7 +256,7 @@ class DiskImageBase(object):
def parse_segments(self):
r = self.rawdata
i = self.header.atr_header_offset
i = self.header.header_offset
if self.header.image_size > 0:
self.segments.append(ObjSegment(r[0:i], 0, 0, 0, i, name="%s Header" % self.header.file_format))
self.segments.append(RawSectorsSegment(r[i:], 1, self.header.max_sectors, self.header.image_size, 128, 3, self.header.sector_size, name="Raw disk sectors"))
@ -327,7 +327,7 @@ class BootDiskImage(DiskImageBase):
return
start, size = self.header.get_pos(1)
b = self.bytes
i = self.header.atr_header_offset
i = self.header.header_offset
flag = b[i:i + 2].view(dtype='<u2')[0]
if flag == 0xffff:
raise InvalidDiskImage("Appears to be an executable")

View File

@ -24,3 +24,6 @@ class ByteNotInFile166(InvalidFile):
class InvalidBinaryFile(InvalidFile):
pass
class InvalidSegmentParser(AtrError):
pass

View File

@ -18,7 +18,7 @@ class KBootDirent(AtariDosDirent):
else:
self.ext = "xex"
start, size = image.header.get_pos(4)
i = image.header.atr_header_offset + 9
i = image.header.header_offset + 9
count = image.bytes[i] + 256 * image.bytes[i+1] + 256 * 256 *image.bytes[i + 2]
if start + count > image.size or start + count < image.size - 128:
self.is_sane = False

93
atrcopy/parsers.py Normal file
View File

@ -0,0 +1,93 @@
import numpy as np
from segments import SegmentData, DefaultSegment
from diskimages import BootDiskImage
from kboot import KBootImage
from ataridos import AtariDosDiskImage, AtariDosFile
from spartados import SpartaDosDiskImage
from errors import *
class SegmentParser(object):
menu_name = ""
image_type = None
def __init__(self, segment_data):
self.image = None
self.segments = []
self.parse(segment_data)
def parse(self, r):
self.segments.append(DefaultSegment(r, 0))
try:
self.image = self.image_type(r)
self.image.parse_segments()
except AtrError:
raise InvalidSegmentParser
self.segments.extend(self.image.segments)
class DefaultSegmentParser(SegmentParser):
menu_name = "Raw Data"
def parse(self, r):
self.segments = [DefaultSegment(r, 0)]
class KBootSegmentParser(SegmentParser):
menu_name = "KBoot Disk Image"
image_type = KBootImage
class AtariDosSegmentParser(SegmentParser):
menu_name = "Atari DOS Disk Image"
image_type = AtariDosDiskImage
class SpartaDosSegmentParser(SegmentParser):
menu_name = "Sparta DOS Disk Image"
image_type = SpartaDosDiskImage
class AtariBootDiskSegmentParser(SegmentParser):
menu_name = "Atari Boot Disk Image"
image_type = BootDiskImage
class XexSegmentParser(SegmentParser):
menu_name = "XEX (Atari 8-bit executable)"
image_type = AtariDosFile
def guess_parser_for(mime, r):
parsers = mime_parsers[mime]
found = None
for parser in parsers:
try:
found = parser(r)
break
except InvalidSegmentParser:
pass
return found
mime_parsers = {
"application/vnd.atari8bit.atr": [
KBootSegmentParser,
SpartaDosSegmentParser,
AtariDosSegmentParser,
AtariBootDiskSegmentParser,
],
"application/vnd.atari8bit.xex": [
XexSegmentParser,
],
}
mime_parse_order = [
"application/vnd.atari8bit.atr",
"application/vnd.atari8bit.xex",
]
known_segment_parsers = [DefaultSegmentParser]
for mime in mime_parse_order:
known_segment_parsers.extend(mime_parsers[mime])

View File

@ -108,13 +108,13 @@ class SpartaDosDirent(AtariDosDirent):
class SpartaDosDiskImage(DiskImageBase):
def __init__(self, bytes, style=None):
def __init__(self, *args, **kwargs):
self.first_bitmap = 0
self.num_bitmap = 0
self.root_dir = 0
self.root_dir_dirent = None
self.fs_version = 0
DiskImageBase.__init__(self, bytes, style)
DiskImageBase.__init__(self, *args, **kwargs)
def __str__(self):
return "%s Sparta DOS Format: %d usable sectors (%d free), %d files" % (self.header, self.total_sectors, self.unused_sectors, len(self.files))