import numpy as np from errors import * from diskimages import DiskImageBase, BaseHeader from segments import SegmentData, EmptySegment, ObjSegment, RawSectorsSegment, DefaultSegment, SegmentedFileSegment, SegmentSaver, get_style_bits from utils import * import logging log = logging.getLogger(__name__) class AtariDosWriteableSector(WriteableSector): @property def next_sector_num(self): return self._next_sector_num @next_sector_num.setter def next_sector_num(self, value): self._next_sector_num = value index = self.sector_size - 3 hi, lo = divmod(value, 256) self.data[index + 0] = (self.file_num << 2) | (hi & 0x03) self.data[index + 1] = lo self.data[index + 2] = self.used if _dbg: log.debug("sector metadata for %d: %s" % (self._sector_num, self.data[index:index + 3])) # file number will be added later when known. class AtariDosVTOC(VTOC): def parse_segments(self, segments): self.vtoc1 = segments[0].data bits = np.unpackbits(self.vtoc1[0x0a:0x64]) self.sector_map[0:720] = bits if _dbg: log.debug("vtoc before:\n%s" % str(self)) def calc_bitmap(self): if _dbg: log.debug("vtoc after:\n%s" % str(self)) packed = np.packbits(self.sector_map[0:720]) self.vtoc1[0x0a:0x64] = packed s = WriteableSector(self.sector_size, self.vtoc1) s.sector_num = 360 self.sectors.append(s) class AtariDosDirectory(Directory): @property def dirent_class(self): return AtariDosDirent def encode_empty(self): return np.zeros([16], dtype=np.uint8) def encode_dirent(self, dirent): data = dirent.encode_dirent() if _dbg: log.debug("encoded dirent: %s" % data) return data def set_sector_numbers(self, image): num = 361 for sector in self.sectors: sector.sector_num = num num += 1 class AtariDosDirent(Dirent): # 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.basename = str(values[3]).rstrip() self.ext = str(values[4]).rstrip() self.is_sane = self.sanity_check(image) def encode_dirent(self): data = np.zeros([self.format.itemsize], dtype=np.uint8) values = data.view(dtype=self.format)[0] flag = (1 * int(self.opened_output)) | (2 * int(self.dos_2)) | (4 * int(self.mydos)) | (0x10 * int(self.is_dir)) | (0x20 * int(self.locked)) | (0x40 * int(self.in_use)) | (0x80 * int(self.deleted)) values[0] = flag values[1] = self.num_sectors values[2] = self.starting_sector values[3] = self.basename values[4] = self.ext return data def mark_deleted(self): self.deleted = True self.in_use = False def update_sector_info(self, sector_list): self.num_sectors = sector_list.num_sectors self.starting_sector = sector_list.first_sector def add_metadata_sectors(self, vtoc, sector_list, header): # no extra sectors are needed for an Atari DOS file; the links to the # next sector is contained in the sector. pass 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 get_sectors_in_vtoc(self, image): sector_list = BaseSectorList(image.header) self.start_read(image) while True: sector = WriteableSector(image.header.sector_size, None, self.current_sector) sector_list.append(sector) _, last, _, _ = self.read_sector(image) if last: break return sector_list 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("Expecting file %d, found %d" % (self.file_num, file_num)) 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 set_values(self, filename, filetype, index): if "." in filename: filename, ext = filename.split(".", 1) else: ext = " " self.basename = "%-8s" % filename[0:8] self.ext = ext self.file_num = index self.dos_2 = True self.in_use = True if _dbg: log.debug("set_values: %s" % self) 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 XexContainerSegment(DefaultSegment): can_resize_default = True 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 strict_check(self): pass def relaxed_check(self): pass def parse_segments(self): r = self.rawdata b = r.get_data() s = r.get_style() pos = 0 style_pos = 0 first = True if _dbg: 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 data, style = self.get_sectors(self.vtoc2) extra_free = data[122:124].view(dtype=' 0: start, count = self.get_contiguous_sectors(self.vtoc2, 1) segment = RawSectorsSegment(r[start:start+count], self.vtoc2, 1, count, 128, 3, self.header.sector_size, name="VTOC2") segment.style[:] = get_style_bits(data=True) segment.set_comment_at(0x00, "Repeat of sectors 48-719") segment.set_comment_at(0x44, "Sector bit map 720-1023") segment.set_comment_at(0x7a, "Number of free sectors above 720") segment.set_comment_at(0x7c, "unused") 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") segment.style[:] = get_style_bits(data=True) index = 0 for filenum in range(64): segment.set_comment_at(index + 0x00, "FILE #%d: Flag" % filenum) segment.set_comment_at(index + 0x01, "FILE #%d: Number of sectors in file" % filenum) segment.set_comment_at(index + 0x03, "FILE #%d: Starting sector number" % filenum) segment.set_comment_at(index + 0x05, "FILE #%d: Filename" % filenum) segment.set_comment_at(index + 0x0d, "FILE #%d: Extension" % filenum) index += 16 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.filename, dirent.num_sectors, dirent.starting_sector) verbose_name = "%s (%d sectors, first@%d) %s" % (dirent.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.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: if _dbg: log.debug("%s not a binary file; skipping segment generation" % str(segment)) return segments_out def create_executable_file_image(self, segments, run_addr=None): base_segment, user_segments = get_xex(segments, run_addr) return base_segment.data, "XEX" class BootDiskImage(AtariDosDiskImage): def __str__(self): return "%s Boot Disk" % (self.header) def check_size(self): if self.header is None: return start, size = self.header.get_pos(1) b = self.bytes i = self.header.header_offset flag = b[i:i + 2].view(dtype=' max_sectors or nsec < 1: raise InvalidDiskImage("Number of boot sectors out of range") if bload < 0x200 or bload > (0xc000 - (nsec * self.header.sector_size)): raise InvalidDiskImage("Bad boot load address") def get_boot_sector_info(self): pass def get_vtoc(self): pass def get_directory(self, directory=None): pass boot_record_type = np.dtype([ ('BFLAG', 'u1'), ('BRCNT', 'u1'), ('BLDADR', '= s.start_addr and run_addr < s.start_addr + len(s): found = True break if not found: raise InvalidBinaryFile("Run address points outside data segments") else: run_addr = segments[0].start_addr words[0] = run_addr r = SegmentData(words.view(dtype=np.uint8)) s = DefaultSegment(r, 0x2e0) segments_copy[0:0] = [s] total += 6 bytes = np.zeros([total], dtype=np.uint8) rawdata = SegmentData(bytes) main_segment = DefaultSegment(rawdata) main_segment.data[0:2] = 0xff # FFFF header main_segment.style[0:2] = data_style i = 2 for s in segments_copy: # create new sub-segment inside new main segment that duplicates the # original segment's data/style new_s = DefaultSegment(rawdata[i:i+4+len(s)], s.start_addr) words = new_s.data[0:4].view(dtype='