#!/usr/bin/env python import struct from cStringIO import StringIO class AtrError(RuntimeError): pass class InvalidAtrHeader(AtrError): pass class LastDirent(AtrError): pass class FileNumberMismatchError164(AtrError): pass class AtrHeader(object): format = " 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 = values[1] self.starting_sector = values[2] self.filename = values[3].rstrip() self.ext = values[4].rstrip() self.current_sector = 0 def __str__(self): output = "o" if self.opened_output else "." dos2 = "2" if self.dos_2 else "." mydos = "m" if self.mydos else "." in_use = "u" if self.in_use else "." deleted = "d" if self.deleted else "." locked = "*" if self.locked else " " flags = "%s%s%s%s%s%s %03d" % (output, dos2, mydos, in_use, deleted, locked, self.starting_sector) if self.in_use: return "File #%-2d (%s) %-8s%-3s %03d" % (self.file_num, flags, self.filename, self.ext, self.num_sectors) return def start_read(self): self.current_sector = self.starting_sector self.current_read = self.num_sectors def read_sector(self, disk): raw = disk.get_raw_bytes(self.current_sector) bytes = self.process_raw_sector(disk, raw) return (bytes, self.current_sector == 0) def process_raw_sector(self, disk, raw): file_num = ord(raw[-3]) >> 2 if file_num != self.file_num: raise FileNumberMismatchError164() self.current_sector = ((ord(raw[-3]) & 0x3) << 8) + ord(raw[-2]) num_bytes = ord(raw[-1]) return raw[0:num_bytes] def get_filename(self): ext = ("." + self.ext) if self.ext else "" return self.filename + ext class MydosDirent(AtrDirent): def process_raw_sector(self, disk, raw): self.current_read -= 1 if self.current_read == 0: self.current_sector = 0 else: self.current_sector += 1 if self.current_sector == disk.first_vtoc: self.current_sector = disk.first_data_after_vtoc return raw class AtrFile(object): pass class AtrDiskImage(object): def __init__(self, fh): self.fh = fh self.header = None self.first_vtoc = 360 self.first_data_after_vtoc = 369 self.total_sectors = 0 self.unused_sectors = 0 self.files = [] self.setup() def __str__(self): return "%s %d total sectors (%d free), %d files" % (self.header, self.total_sectors, self.unused_sectors, len(self.files)) 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.fh.seek(0, 2) self.size = self.fh.tell() self.read_atr_header() self.check_size() self.get_vtoc() self.get_directory() def read_atr_header(self): self.fh.seek(0) bytes = self.fh.read(16) try: self.header = AtrHeader(bytes) except InvalidAtrHeader: self.header = XfdHeader() def check_size(self): self.header.check_size(self.size) self.initial_sector_size = self.header.sector_size self.num_initial_sectors = 0 def get_pos(self, sector): if sector <= self.num_initial_sectors: pos = self.num_initial_sectors * (sector - 1) size = self.initial_sector_size else: pos = self.num_initial_sectors * self.initial_sector_size + (sector - 1 - self.num_initial_sectors) * self.header.sector_size size = self.header.sector_size pos += self.header.atr_header_offset return pos, size def get_raw_bytes(self, sector): pos, size = self.get_pos(sector) self.fh.seek(pos) raw = self.fh.read(size) return raw def get_sectors(self, start, end=None): """ Get contiguous sectors :param start: first sector number to read (note: numbering starts from 1) :param end: last sector number to read :returns: bytes """ output = StringIO() pos, size = self.get_pos(start) self.fh.seek(pos) if end is None: end = start while start <= end: bytes = self.fh.read(size) output.write(bytes) start += 1 pos, size = self.get_pos(start) return output.getvalue() def get_vtoc(self): bytes = self.get_sectors(360) values = struct.unpack("