import numpy as np from errors import * from diskimages import DiskImageBase from segments import EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentSaver from utils import to_numpy import logging log = logging.getLogger(__name__) class AtariDosDirent(object): # ATR Dirent structure described at http://atari.kensclassics.org/dos.htm format = np.dtype([ ('FLAG', 'u1'), ('COUNT', ' 0 self.dos_2 = (flag&0x02) > 0 self.mydos = (flag&0x04) > 0 self.is_dir = (flag&0x10) > 0 self.locked = (flag&0x20) > 0 self.in_use = (flag&0x40) > 0 self.deleted = (flag&0x80) > 0 self.num_sectors = int(values[1]) self.starting_sector = int(values[2]) self.filename = str(values[3]).rstrip() self.ext = str(values[4]).rstrip() self.is_sane = self.sanity_check(image) def sanity_check(self, image): if not self.in_use: return True if not image.header.sector_is_valid(self.starting_sector): return False if self.num_sectors < 0 or self.num_sectors > image.header.max_sectors: return False return True def start_read(self, image): if not self.is_sane: raise InvalidDirent("Invalid directory entry '%s'" % str(self)) self.current_sector = self.starting_sector self.current_read = self.num_sectors self.sectors_seen = set() def read_sector(self, image): raw, pos, size = image.get_raw_bytes(self.current_sector) bytes, num_data_bytes = self.process_raw_sector(image, raw) return bytes, self.current_sector == 0, pos, num_data_bytes def process_raw_sector(self, image, raw): file_num = raw[-3] >> 2 if file_num != self.file_num: raise FileNumberMismatchError164() self.sectors_seen.add(self.current_sector) next_sector = ((raw[-3] & 0x3) << 8) + raw[-2] if next_sector in self.sectors_seen: raise InvalidFile("Bad sector pointer data: attempting to reread sector %d" % next_sector) self.current_sector = next_sector num_bytes = raw[-1] return raw[0:num_bytes], num_bytes def get_filename(self): ext = ("." + self.ext) if self.ext else "" return self.filename + ext class MydosDirent(AtariDosDirent): def process_raw_sector(self, image, raw): # No file number stored in the sector data; two full bytes available # for next sector self.current_sector = (raw[-3] << 8) + raw[-2] num_bytes = raw[-1] return raw[0:num_bytes], num_bytes class XexSegmentSaver(SegmentSaver): export_data_name = "Atari 8-bit Executable" export_extensions = [".xex"] class XexSegment(ObjSegment): savers = [SegmentSaver, XexSegmentSaver] class AtariDosFile(object): """Parse a binary chunk into segments according to the Atari DOS object file format. Ref: http://www.atarimax.com/jindroush.atari.org/afmtexe.html """ def __init__(self, rawdata): self.rawdata = rawdata self.size = len(rawdata) self.segments = [] self.files = [] def __str__(self): return "\n".join(str(s) for s in self.segments) + "\n" def parse_segments(self): r = self.rawdata b = r.get_data() pos = 0 first = True log.debug("Initial parsing: size=%d" % self.size) while pos < self.size: if pos + 1 < self.size: header, = b[pos:pos+2].view(dtype=' self.calc_vtoc_code(): raise InvalidDiskImage("Invalid number of VTOC sectors: %d" % num) self.total_sectors = values[1] self.unused_sectors = values[2] if self.header.image_size == 133120: # enhanced density has 2nd VTOC self.vtoc2 = 1024 extra_free = self.get_sectors(self.vtoc2)[122:124].view(dtype=' 0: start, count = self.get_contiguous_sectors(self.vtoc2, 1) segment = RawSectorsSegment(r[start:start+count], self.vtoc2, 1, count, self.header.sector_size, name="VTOC2") segments.append(segment) return segments def get_directory_segments(self): r = self.rawdata segments = [] addr = 0 start, count = self.get_contiguous_sectors(361, 8) segment = RawSectorsSegment(r[start:start+count], 361, 8, count, 128, 3, self.header.sector_size, name="Directory") segments.append(segment) return segments def get_file_segment(self, dirent): byte_order = [] dirent.start_read(self) while True: bytes, last, pos, size = dirent.read_sector(self) byte_order.extend(range(pos, pos + size)) if last: break if len(byte_order) > 0: name = "%s %ds@%d" % (dirent.get_filename(), dirent.num_sectors, dirent.starting_sector) verbose_name = "%s (%d sectors, first@%d) %s" % (dirent.get_filename(), dirent.num_sectors, dirent.starting_sector, dirent.verbose_info) raw = self.rawdata.get_indexed(byte_order) segment = DefaultSegment(raw, name=name, verbose_name=verbose_name) else: segment = EmptySegment(self.rawdata, name=dirent.get_filename()) return segment def get_file_segments(self): segments_in = DiskImageBase.get_file_segments(self) segments_out = [] for segment in segments_in: segments_out.append(segment) try: binary = AtariDosFile(segment.rawdata) segments_out.extend(binary.segments) except InvalidBinaryFile: log.debug("%s not a binary file; skipping segment generation" % str(segment)) return segments_out def get_xex(segments, runaddr): total = 2 for s in segments: total += 4 + len(s) total += 6 bytes = np.zeros([total], dtype=np.uint8) bytes[0:2] = 0xff # FFFF header i = 2 for s in segments: words = bytes[i:i+4].view(dtype='