#!/usr/bin/env python2.7 # xdftool # swiss army knife for adf and hdf amiga disk images import sys import argparse import os.path from amitools.fs.ADFSVolume import ADFSVolume from amitools.fs.blkdev.BlkDevFactory import BlkDevFactory from amitools.fs.FSError import * from amitools.fs.Imager import Imager from amitools.fs.Repacker import Repacker from amitools.fs.block.BootBlock import BootBlock from amitools.fs.block.RootBlock import RootBlock from amitools.util.CommandQueue import CommandQueue from amitools.util.HexDump import * from amitools.util.DisAsm import DisAsm import amitools.util.KeyValue as KeyValue from amitools.fs.rdb.RDisk import RDisk from amitools.fs.FSString import FSString import amitools.fs.DosType as DosType # system encoding def make_fsstr(s): # fetch default encoding (if available) encoding = sys.stdin.encoding if encoding is None: # assume win default encoding if os.platform == "win32": encoding = "cp1252" else: encoding = "utf-8" u = s.decode(encoding) return FSString(u) # ----- commands ----- class Command: def __init__(self, args, opts, edit=False): self.args = args self.opts = opts self.edit = edit self.exit_code = 0 self.volume = None self.blkdev = None def run(self, blkdev, vol): # optional init blkdev function if hasattr(self, "init_blkdev"): if blkdev == None: self.blkdev = self.init_blkdev(self.args.image_file) if self.blkdev == None: return 5 blkdev = self.blkdev # optional init volume function if hasattr(self, "init_vol"): # close old if vol != None: vol.close() # create new volume self.volume = self.init_vol(blkdev) if self.volume == None: return 6 vol = self.volume # common handler if hasattr(self, 'handle_blkdev'): return self.handle_blkdev(blkdev) elif hasattr(self, 'handle_vol'): return self.handle_vol(vol) else: return 0 def has_init_blkdev(self): return hasattr(self, 'init_blkdev') def need_volume(self): return hasattr(self, 'handle_vol') and not hasattr(self, 'init_vol') # ----- command handler ----- class FSCommandQueue(CommandQueue): def __init__(self, args, cmd_list, sep, cmd_map): CommandQueue.__init__(self, cmd_list, sep, cmd_map) self.args = args self.blkdev = None self.volume = None def run(self): self.img = self.args.image_file try: # main command loop exit_code = CommandQueue.run(self) except FSError as e: cmd = "'%s'" % " ".join(self.cmd_line) print cmd,"FSError:",e exit_code = 3 except IOError as e: cmd = "'%s'" % " ".join(self.cmd_line) print cmd,"IOError:",e exit_code = 4 finally: # close volume if self.volume != None: self.volume.close() if self.args.verbose: print "closing volume:",self.img # close blkdev if self.blkdev != None: self.blkdev.close() if self.args.verbose: print "closing image:",self.img return exit_code def create_cmd(self, cclass, name, opts): return cclass(self.args, opts) def _open_volume(self): # setup volume if self.volume == None: self.volume = ADFSVolume(self.blkdev) if self.args.verbose: print "opening volume:",self.img self.volume.open() def run_first(self, cmd_line, cmd): self.cmd_line = cmd_line # check if first command is an init command if not cmd.has_init_blkdev(): # auto add 'open' command pre_cmd = OpenCmd(self.args, []) if self.args.verbose: print "auto open command:",self.cmd_line exit_code = pre_cmd.run(self.blkdev, self.volume) if self.args.verbose: print "auto open exit_code:",exit_code if exit_code != 0: return exit_code self.blkdev = pre_cmd.blkdev # setup volume (if necessary) if cmd.need_volume(): self._open_volume() # run first command if self.args.verbose: print "command:",self.cmd_line if cmd.edit and self.args.read_only: raise IOError("Edit commands not allowed in read-only mode") # check code of command after __init__ parsing if cmd.exit_code != 0: return cmd.exit_code # perform command exit_code = cmd.run(self.blkdev, self.volume) if cmd.blkdev != None: self.blkdev = cmd.blkdev if cmd.volume != None: self.volume = cmd.volume # final exit code if self.args.verbose: print "exit_code:",exit_code return exit_code def run_next(self, cmd_line, cmd): self.cmd_line = cmd_line if self.args.verbose: print "command:",self.cmd_line # verify command if cmd.edit and self.args.read_only: raise IOError("Edit commands not allowed in read-only mode") # make sure volume is set up if self.volume == None and cmd.need_volume(): self._open_volume() # run command exit_code = cmd.run(self.blkdev, self.volume) if cmd.blkdev != None: self.blkdev = cmd.blkdev if cmd.volume != None: self.volume = cmd.volume if self.args.verbose: print "exit_code:",exit_code return exit_code # ----- Init: Open/Create ----- class OpenCmd(Command): def init_blkdev(self, image_file): opts = KeyValue.parse_key_value_strings(self.opts) f = BlkDevFactory() return f.open(image_file, options=opts, read_only=self.args.read_only) class CreateCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def init_blkdev(self, image_file): opts = KeyValue.parse_key_value_strings(self.opts) f = BlkDevFactory() return f.create(image_file, options=opts, force=self.args.force) class FormatCmd(Command): def init_blkdev(self, image_file): opts = KeyValue.parse_key_value_strings(self.opts[1:]) f = BlkDevFactory() return f.create(image_file, options=opts, force=self.args.force) def init_vol(self, blkdev): vol = ADFSVolume(blkdev) n = len(self.opts) if n < 1 or n > 2: print "Usage: format [dos_type]" return None else: if n > 1: dos_str = self.opts[1] dos_type = DosType.parse_dos_type_str(dos_str) if dos_type is None: print "ERROR invalid dos_tpye:", dos_str return None else: dos_type = None vol_name = make_fsstr(self.opts[0]) vol.create(vol_name, dos_type=dos_type) return vol # ----- Pack/Unpack ----- class PackCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) self.imager = Imager() n = len(self.opts) if n == 0: print "Usage: pack [dos_type] [out_size]" self.exit_code = 1 else: self.in_path = self.opts[0] blkdev_opts = None dos_type = None if n > 1: # is a dostype given? dos_str = opts[1] dos_type = DosType.parse_dos_type_str(dos_str) if dos_type is not None: begin = 2 else: begin = 1 # take remainder as blkdev opts blkdev_opts = KeyValue.parse_key_value_strings(opts[begin:]) self.blkdev_opts = blkdev_opts self.dos_type = dos_type self.imager.pack_begin(self.in_path) def init_blkdev(self, image_file): return self.imager.pack_create_blkdev(self.in_path, image_file, force=self.args.force, options=self.blkdev_opts) def init_vol(self, blkdev): return self.imager.pack_create_volume(self.in_path, blkdev, dos_type=self.dos_type) def handle_vol(self, volume): self.imager.pack_root(self.in_path, volume) self.imager.pack_end(self.in_path, volume) if self.args.verbose: print "Packed %d bytes" % (self.imager.get_total_bytes()) return 0 class UnpackCmd(Command): def handle_vol(self, vol): n = len(self.opts) if n == 0: print "Usage: unpack " return 1 else: out_path = self.opts[0] img = Imager() img.unpack(vol, out_path) if self.args.verbose: print "Unpacked %d bytes" % (img.get_total_bytes()) return 0 class RepackCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) n = len(self.opts) if n == 0: print "Usage: repack [in_size]" self.exit_code = 1 in_img = self.opts[0] in_opts = KeyValue.parse_key_value_strings(self.opts[1:]) self.repacker = Repacker(in_img, in_opts) if not self.repacker.create_in(): self.exit_code = 2 def init_blkdev(self, image_file): return self.repacker.create_out_blkdev(image_file) def init_vol(self, blkdev): return self.repacker.create_out_volume(blkdev) def handle_vol(self, vol): self.repacker.repack() return 0 # ----- Query Image ----- # list: list directory tree class ListCmd(Command): def handle_vol(self, vol): n = len(self.opts) if n == 0: vol.root_dir.list(all=True) show_info = True show_all = True node = vol.get_root_dir() else: name = make_fsstr(self.opts[0]) node = vol.get_path_name(name) if node != None: show_all = "all" in self.opts show_detail = "detail" in self.opts show_info = "info" in self.opts node.list(all=show_all, detail=show_detail) else: print "ERROR path not found:",node return 2 if show_info: info = node.get_info(show_all) for line in info: print line return 0 class TypeCmd(Command): def handle_vol(self, vol): p = self.opts if len(p) == 0: print "Usage: type " return 1 else: name = make_fsstr(p[0]) data = vol.read_file(name) print data return 0 class ReadCmd(Command): def handle_vol(self, vol): p = self.opts n = len(p) if n == 0 or n > 2: print "Usage: read [sys_file]" return 1 # determine output name out_name = os.path.basename(p[0]) if n == 2: if os.path.isdir(p[1]): out_name = os.path.join(p[1],out_name) else: out_name = p[1] # single file operation name = make_fsstr(p[0]) node = vol.get_path_name(name) if node == None: print "Node not found:",p[0] return 2 # its a file if node.is_file(): data = node.get_file_data() # write data to file fh = open(out_name,"wb") fh.write(data) fh.close() # its a dir elif node.is_dir(): img = Imager() img.unpack_dir(node, out_name) node.flush() return 0 class InfoCmd(Command): def handle_vol(self, vol): info = vol.get_info() for line in info: print line return 0 # ----- Edit Image ----- class MakeDirCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): if len(self.opts) != 1: print "Usage: mkdir " return 1 else: dir_path = make_fsstr(self.opts[0]) vol.create_dir(dir_path) return 0 class WriteCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): n = len(self.opts) if n == 0 or n > 2: print "Usage: write [ami_path]" return 1 # get file_name and ami_path sys_file = self.opts[0] file_name = os.path.basename(sys_file) if n > 1: ami_path = self.opts[1] else: ami_path = os.path.basename(sys_file) # check sys path if not os.path.exists(sys_file): print "File not found:",sys_file return 2 ami_path = make_fsstr(ami_path) file_name = make_fsstr(file_name) # handle file if os.path.isfile(sys_file): fh = open(sys_file,"rb") data = fh.read() fh.close() vol.write_file(data, ami_path, file_name) # handle dir elif os.path.isdir(sys_file): parent_node, dir_name = vol.get_create_path_name(ami_path,file_name) if parent_node == None: print "Invalid path",ami_path return 2 node = parent_node.create_dir(dir_name) img = Imager() img.pack_dir(sys_file, node) return 0 class DeleteCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): n = len(self.opts) if n == 0: print "Usage: delete [wipe] [all]" return 1 do_wipe = 'wipe' in self.opts do_all = 'all' in self.opts path = make_fsstr(self.opts[0]) node = vol.delete(path, wipe=do_wipe, all=do_all) return 0 class ProtectCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): n = len(self.opts) if n != 2: print "Usage: protect " return 1 name = make_fsstr(self.opts[0]) pr_str = self.opts[1] node = vol.get_path_name(name) if node != None: node.change_protect_by_string(pr_str) return 0 else: print "Can't find node:",name return 2 class CommentCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): n = len(self.opts) if n != 2: print "Usage: comment " return 1 name = make_fsstr(self.opts[0]) comment = make_fsstr(self.opts[1]) node = vol.get_path_name(name) if node != None: node.change_comment(comment) return 0 else: print "Can't find node:",name return 2 class TimeCmd(Command): def __init__(self, args, opts): Command.__init__(self, args, opts, edit=True) def handle_vol(self, vol): n = len(self.opts) if n != 2: print "Usage: time