Files
AmigaExamples/tools/external/amitools/xdftool.py
T
2016-05-29 19:28:59 +10:00

827 lines
22 KiB
Python
Executable File

#!/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 <volume_name> [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 <in_path> [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 <out_path>"
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 <src_path> [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 <ami_file>"
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 <ami_file|dir> [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 <dir_path>"
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 <sys_file|dir> [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 <ami_path> [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 <ami_file> <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 <ami_file> <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 <ami_file> <time>"
return 1
name = self.opts[0]
tstr = self.opts[1]
node = vol.get_path_name(name)
if node != None:
node.change_mod_ts_by_string(tstr)
return 0
else:
print "Can't find node:",name
return 2
class RelabelCmd(Command):
def __init__(self, args, opts):
Command.__init__(self, args, opts, edit=True)
def handle_vol(self, vol):
n = len(self.opts)
if n != 1:
print "Usage: relabel <new_name>"
return 1
name = self.opts[0]
node = vol.relabel(name)
return 0
# ----- Block Tools -----
class BlockCmd(Command):
def handle_vol(self, vol):
n = len(self.opts)
if n == 0:
print "Usage: block ( boot | root | node <ami_file> [data] | dump <block_no> )"
return 1
cmd = self.opts[0]
if cmd == 'boot':
vol.boot.dump()
return 0
elif cmd == 'root':
vol.root.dump()
return 0
elif cmd == 'node':
if n == 1:
print "No node given!"
return 1
else:
name = make_fsstr(self.opts[1])
node = vol.get_path_name(name)
if node != None:
with_data = "data" in self.opts
node.dump_blocks(with_data)
return 0
else:
print "Can't find node:",name
return 2
elif cmd == 'dump':
if n == 1:
print "No block number given!"
return 1
else:
block_no = int(self.opts[1])
data = vol.blkdev.read_block(block_no)
print_hex(data)
# ----- Bitmap Tools -----
class BitmapCmd(Command):
def handle_vol(self, vol):
n = len(self.opts)
if n == 0:
print "Usage: bitmap ( free | used | find [n] | all | maps | root [all] | node <path> [all] [entries]) [brief]"
return 1
cmd = self.opts[0]
# brief mode
brief = False
if self.opts[-1] == 'brief':
brief = True
self.opts = self.opts[:-1]
if cmd == 'free':
vol.bitmap.print_free(brief)
return 0
elif cmd == 'used':
vol.bitmap.print_used(brief)
return 0
elif cmd == 'find':
if n == 2:
num = int(self.opts[1])
blk_nums = vol.bitmap.find_n_free(num)
if blk_nums == None:
print "No %d free blocks found" % num
return 100
else:
print "Free %d blocks:" % num,blk_nums
return 0
else:
blk_num = vol.bitmap.find_free()
if blk_num == None:
print "No free block found"
return 100
else:
print "Free block:",blk_num
return 0
elif cmd == 'all':
bm = vol.bitmap.create_draw_bitmap()
vol.bitmap.draw_on_bitmap(bm)
vol.root_dir.draw_on_bitmap(bm, True)
vol.bitmap.print_draw_bitmap(bm, brief)
return 0
elif cmd == 'maps':
bm = vol.bitmap.create_draw_bitmap()
vol.bitmap.draw_on_bitmap(bm)
vol.bitmap.print_draw_bitmap(bm, brief)
return 0
elif cmd == 'root':
show_entries = 'entries' in self.opts
bm = vol.bitmap.create_draw_bitmap()
vol.root_dir.draw_on_bitmap(bm, False, show_entries)
vol.bitmap.print_draw_bitmap(bm, brief)
return 0
elif cmd == 'node':
if n > 1:
name = make_fsstr(self.opts[1])
node = vol.get_path_name(name)
if node != None:
show_all = 'all' in self.opts
show_entries = 'entries' in self.opts
bm = vol.bitmap.create_draw_bitmap()
node.draw_on_bitmap(bm, show_all, show_entries)
vol.bitmap.print_draw_bitmap(bm, brief)
return 0
else:
print "Node '%s' not found!" % self.opts[1]
return 2
else:
print "Need node path!"
return 1
else:
print "Unknown bitmap command!"
return 1
# ----- Root Tools -----
class RootCmd(Command):
def handle_vol(self, volume):
n = len(self.opts)
if n == 0:
print "Usage: root ( show | create_time <time> | disk_time <time> | time <time> )"
return 1
cmd = self.opts[0]
# root show
if cmd == 'show':
volume.root.dump()
return 0
# create_time <time>
elif cmd == 'create_time':
if n != 2:
print "create_time <time>"
return 1
else:
volume.change_create_ts_by_string(self.opts[1])
return 0
# disk_time <time>
elif cmd == 'disk_time':
if n != 2:
print "disk_time <time>"
return 1
else:
volume.change_disk_ts_by_string(self.opts[1])
return 0
# time <time>
elif cmd == 'time':
if n != 2:
print "time <time>"
return 1
else:
volume.change_mod_ts_by_string(self.opts[1])
return 0
# boot ?
else:
print "Unknown root command!"
return 1
# ----- Boot Tools -----
class BootCmd(Command):
def handle_blkdev(self, blkdev):
n = len(self.opts)
if n == 0:
print "Usage: boot ( show [hex] [asm] | read <file> | write <file> | install [boot1x] | clear )"
return 1
cmd = self.opts[0]
# fetch boot blcok
bb = BootBlock(blkdev, 0)
bb.read()
# boot show [hex] [asm]
if cmd == 'show':
bb.dump()
if bb.valid and bb.boot_code != None:
if 'hex' in self.opts:
print_hex(bb.boot_code)
if 'asm' in self.opts:
dis = DisAsm()
code = dis.disassemble(bb.boot_code)
dis.dump(code)
return 0
# boot read <file>
elif cmd == 'read':
if n > 1:
if bb.valid:
if bb.boot_code != None:
f = open(self.opts[1], "wb")
f.write(bb.boot_code)
f.close()
return 0
else:
print "No Boot Code found!"
return 2
else:
print "Invalid Boot Block!"
return 1
else:
print "No file name!"
return 1
# boot write <file>
elif cmd == 'write':
if n > 1:
f = open(self.opts[1], "rb")
data = f.read()
f.close()
ok = bb.set_boot_code(data)
if ok:
bb.write()
return 0
else:
print "Boot Code invalid!"
return 2
else:
print "No file name!"
return 1
# boot install [<name>]
elif cmd == 'install':
if n == 1: # default boot code
name = "boot2x3x"
else:
name = self.opts[1]
# boot code directory
bc_dir = bb.get_boot_code_dir()
if bc_dir == None:
print "No boot code directory found!"
return 1
path = os.path.join(bc_dir, name + ".bin")
if not os.path.exists:
print "Boot code '%s' not found!" % path
return 1
# read data
f = open(path,"rb")
data = f.read()
f.close()
ok = bb.set_boot_code(data)
if ok:
bb.write()
return 0
else:
print "Boot Code invalid!"
return 2
# boot clear
elif cmd == 'clear':
bb.set_boot_code(None)
bb.write()
return 0
# boot ?
else:
print "Unknown boot command!"
return 1
# ----- BlkDev Command -----
class BlkDevCmd(Command):
def handle_blkdev(self, blkdev):
blkdev.dump()
return 0
# ----- main -----
def main():
# call scanner and process all files with selected command
cmd_map = {
"open" : OpenCmd,
"create" : CreateCmd,
"list" : ListCmd,
"type" : TypeCmd,
"read" : ReadCmd,
"makedir" : MakeDirCmd,
"write" : WriteCmd,
"delete" : DeleteCmd,
"format" : FormatCmd,
"bitmap" : BitmapCmd,
"blkdev" : BlkDevCmd,
"protect" : ProtectCmd,
"comment" : CommentCmd,
"time" : TimeCmd,
"block" : BlockCmd,
"pack" : PackCmd,
"unpack" : UnpackCmd,
"repack" : RepackCmd,
"boot" : BootCmd,
"root" : RootCmd,
"info" : InfoCmd,
"relabel" : RelabelCmd
}
parser = argparse.ArgumentParser()
parser.add_argument('image_file')
parser.add_argument('command_list', nargs='+', help="command: "+",".join(cmd_map.keys()))
parser.add_argument('-v', '--verbose', action='store_true', default=False, help="be more verbos")
parser.add_argument('-s', '--seperator', default='+', help="set the command separator char sequence")
parser.add_argument('-r', '--read-only', action='store_true', default=False, help="read-only operation")
parser.add_argument('-f', '--force', action='store_true', default=False, help="force overwrite existing image")
args = parser.parse_args()
cmd_list = args.command_list
sep = args.seperator
queue = FSCommandQueue(args, cmd_list, sep, cmd_map)
code = queue.run()
return code
if __name__ == '__main__':
sys.exit(main())