2017-05-02 21:18:29 +00:00
import os
2017-05-02 18:17:32 +00:00
import sys
2017-05-04 17:09:15 +00:00
import zlib
2017-05-25 13:50:27 +00:00
import json
2017-05-02 18:17:32 +00:00
2016-04-13 00:13:33 +00:00
import logging
2017-05-02 18:17:32 +00:00
log = logging . getLogger ( __name__ )
2016-02-13 04:36:33 +00:00
2017-05-07 20:28:15 +00:00
from . _metadata import __version__
2017-05-03 02:43:21 +00:00
2016-02-13 04:36:33 +00:00
try :
import numpy as np
except ImportError :
raise RuntimeError ( " atrcopy %s requires numpy " % __version__ )
2018-06-24 19:10:59 +00:00
from . import errors
2017-05-07 20:28:15 +00:00
from . ataridos import AtrHeader , AtariDosDiskImage , BootDiskImage , AtariDosFile , XexContainerSegment , get_xex , add_atr_header
from . dos33 import Dos33DiskImage
from . kboot import KBootImage , add_xexboot_header
from . segments import SegmentData , SegmentSaver , DefaultSegment , EmptySegment , ObjSegment , RawSectorsSegment , SegmentedFileSegment , user_bit_mask , match_bit_mask , comment_bit_mask , data_style , selected_bit_mask , diff_bit_mask , not_user_bit_mask , interleave_segments , SegmentList , get_style_mask , get_style_bits
from . spartados import SpartaDosDiskImage
from . cartridge import A8CartHeader , AtariCartImage
2018-06-25 06:07:47 +00:00
from . parsers import SegmentParser , DefaultSegmentParser , guess_parser_for_mime , guess_parser_for_system , guess_container , iter_parsers , iter_known_segment_parsers , mime_parse_order , parsers_for_filename
2018-05-01 05:28:47 +00:00
from . magic import guess_detail_for_mime
2017-05-07 20:28:15 +00:00
from . utils import to_numpy , text_to_int
2016-02-13 05:36:08 +00:00
2016-02-13 04:36:33 +00:00
def process ( image , dirent , options ) :
skip = False
action = " copying to "
filename = dirent . get_filename ( )
outfilename = filename
if options . no_sys :
if dirent . ext == " SYS " :
skip = True
action = " skipping system file "
if not skip :
if options . xex :
outfilename = " %s %s .XEX " % ( dirent . filename , dirent . ext )
if options . lower :
outfilename = outfilename . lower ( )
2017-03-23 17:06:37 +00:00
2016-02-13 04:36:33 +00:00
if options . dry_run :
action = " DRY_RUN: %s " % action
skip = True
if options . extract :
2017-05-07 20:28:15 +00:00
print ( " %s : %s %s " % ( dirent , action , outfilename ) )
2016-02-13 04:36:33 +00:00
if not skip :
bytes = image . get_file ( dirent )
with open ( outfilename , " wb " ) as fh :
fh . write ( bytes )
else :
2017-05-07 20:28:15 +00:00
print ( dirent )
2016-02-13 04:36:33 +00:00
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
def find_diskimage ( filename ) :
2018-06-25 18:35:38 +00:00
with open ( filename , " rb " ) as fh :
if options . verbose :
print ( " Loading file %s " % filename )
data = to_numpy ( fh . read ( ) )
parser = None
container = guess_container ( data , options . verbose )
if container is not None :
data = container . unpacked
rawdata = SegmentData ( data )
for mime in mime_parse_order :
if options . verbose :
print ( " Trying MIME type %s " % mime )
parser = guess_parser_for_mime ( mime , rawdata , options . verbose )
if parser is None :
continue
if options . verbose :
print ( " Found parser %s " % parser . menu_name )
mime2 = guess_detail_for_mime ( mime , rawdata , parser )
if mime != mime2 and options . verbose :
print ( " Signature match: %s " % mime2 )
break
if parser is None :
raise errors . UnsupportedDiskImage ( " Unknown disk image type " )
2017-03-07 18:43:46 +00:00
else :
parser . image . filename = filename
parser . image . ext = " "
return parser
2017-02-24 19:57:51 +00:00
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
def extract_files ( image , files ) :
2017-05-03 03:02:54 +00:00
if options . all :
files = image . files
2017-02-24 19:57:51 +00:00
for name in files :
try :
dirent = image . find_dirent ( name )
2018-06-24 19:10:59 +00:00
except errors . FileNotFound :
2017-05-07 20:28:15 +00:00
print ( " %s not in %s " % ( name , image ) )
2017-02-24 19:57:51 +00:00
continue
2017-05-02 21:18:29 +00:00
output = dirent . filename
if options . lower :
output = output . lower ( )
2017-02-24 19:57:51 +00:00
if not options . dry_run :
data = image . get_file ( dirent )
2017-05-02 21:18:29 +00:00
if os . path . exists ( output ) and not options . force :
2017-05-07 20:28:15 +00:00
print ( " skipping %s , file exists. Use -f to overwrite " % output )
2017-05-02 21:18:29 +00:00
continue
2017-05-07 20:28:15 +00:00
print ( " extracting %s -> %s " % ( name , output ) )
2017-05-02 21:18:29 +00:00
with open ( output , " wb " ) as fh :
2017-02-24 19:57:51 +00:00
fh . write ( data )
2017-05-02 21:18:29 +00:00
else :
2017-05-07 20:28:15 +00:00
print ( " extracting %s -> %s " % ( name , output ) )
2017-02-24 19:57:51 +00:00
2017-03-23 17:06:37 +00:00
2017-02-26 20:14:23 +00:00
def save_file ( image , name , filetype , data ) :
try :
dirent = image . find_dirent ( name )
if options . force :
image . delete_file ( name )
else :
2017-05-07 20:28:15 +00:00
print ( " skipping %s , use -f to overwrite " % ( name ) )
2017-02-26 20:14:23 +00:00
return False
2018-06-24 19:10:59 +00:00
except errors . FileNotFound :
2017-02-26 20:14:23 +00:00
pass
2017-05-07 20:28:15 +00:00
print ( " copying %s to %s " % ( name , image . filename ) )
2017-02-26 20:14:23 +00:00
if not options . dry_run :
image . write_file ( name , filetype , data )
return True
return False
2017-02-24 19:57:51 +00:00
def add_files ( image , files ) :
filetype = options . filetype
if not filetype :
filetype = image . default_filetype
changed = False
for name in files :
2017-02-26 20:14:23 +00:00
with open ( name , " rb " ) as fh :
data = fh . read ( )
changed = save_file ( image , name , filetype , data )
2017-02-24 19:57:51 +00:00
if changed :
image . save ( )
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
def remove_files ( image , files ) :
changed = False
for name in files :
try :
dirent = image . find_dirent ( name )
2018-06-24 19:10:59 +00:00
except errors . FileNotFound :
2017-05-07 20:28:15 +00:00
print ( " %s not in %s " % ( name , image ) )
2017-02-24 19:57:51 +00:00
continue
2017-05-07 20:28:15 +00:00
print ( " removing %s from %s " % ( name , image ) )
2017-02-24 19:57:51 +00:00
if not options . dry_run :
image . delete_file ( name )
changed = True
if changed :
image . save ( )
2017-03-23 17:06:37 +00:00
2017-05-04 19:44:44 +00:00
def list_files ( image , files , show_crc = False , show_metadata = False ) :
2017-02-24 19:57:51 +00:00
files = set ( files )
for dirent in image . files :
if not files or dirent . filename in files :
2017-05-04 19:44:44 +00:00
if show_crc :
2017-05-04 17:09:15 +00:00
data = image . get_file ( dirent )
crc = zlib . crc32 ( data ) & 0xffffffff # correct for some platforms that return signed int
extra = " %08x " % crc
else :
extra = " "
print ( " %s %s " % ( dirent , extra ) )
2017-05-04 19:44:44 +00:00
if show_metadata :
2017-05-07 20:28:15 +00:00
print ( dirent . extra_metadata ( image ) )
2017-02-24 19:57:51 +00:00
2017-03-23 17:06:37 +00:00
2017-05-04 17:09:15 +00:00
def crc_files ( image , files ) :
files = set ( files )
for dirent in image . files :
if not files or dirent . filename in files :
data = image . get_file ( dirent )
crc = zlib . crc32 ( data ) & 0xffffffff # correct for some platforms that return signed int
print ( " %s : %08x " % ( dirent . filename , crc ) )
2017-05-10 00:58:16 +00:00
def assemble_segments ( source_files , data_files , obj_files , run_addr = " " ) :
2017-02-26 20:31:34 +00:00
if source_files :
try :
import pyatasm
except ImportError :
2018-06-24 19:10:59 +00:00
raise errors . AtrError ( " Please install pyatasm to compile code. " )
2017-02-26 20:14:23 +00:00
changed = False
2017-02-26 20:31:34 +00:00
segments = SegmentList ( )
for name in source_files :
2017-02-26 20:14:23 +00:00
try :
asm = pyatasm . Assemble ( name )
2017-05-07 20:28:15 +00:00
except SyntaxError as e :
2018-06-24 19:10:59 +00:00
raise errors . AtrError ( " Assembly error: %s " % e . msg )
2017-05-02 18:17:32 +00:00
log . debug ( " Assembled %s into: " % name )
2017-02-26 20:14:23 +00:00
for first , last , object_code in asm . segments :
2017-02-26 20:31:34 +00:00
s = segments . add_segment ( object_code , first )
2017-05-02 18:17:32 +00:00
log . debug ( " %s " % s . name )
2017-05-07 20:28:15 +00:00
print ( " adding %s from %s assembly " % ( s , name ) )
2017-02-26 20:31:34 +00:00
for name in data_files :
if " @ " not in name :
2018-06-24 19:10:59 +00:00
raise errors . AtrError ( " Data files must include a load address specified with the @ char " )
2017-02-26 20:31:34 +00:00
name , addr = name . rsplit ( " @ " , 1 )
first = text_to_int ( addr )
2017-05-02 18:17:32 +00:00
log . debug ( " Adding data file %s at $ %04x " % ( name , first ) )
2017-05-07 20:28:15 +00:00
subset = slice ( 0 , sys . maxsize )
2017-05-01 15:41:23 +00:00
if " [ " in name and " ] " in name :
name , slicetext = name . rsplit ( " [ " , 1 )
if " : " in slicetext :
start , end = slicetext . split ( " : " , 1 )
try :
start = int ( start )
except :
start = 0
if end . endswith ( " ] " ) :
end = end [ : - 1 ]
try :
end = int ( end )
except :
end = None
subset = slice ( start , end )
2017-02-26 20:31:34 +00:00
with open ( name , ' rb ' ) as fh :
2017-05-01 15:41:23 +00:00
data = fh . read ( ) [ subset ]
2017-02-26 20:31:34 +00:00
s = segments . add_segment ( data , first )
2017-05-02 18:17:32 +00:00
log . debug ( " read data for %s " % s . name )
2017-05-06 23:49:42 +00:00
for name in obj_files :
2018-06-25 18:35:38 +00:00
try :
parser = find_diskimage ( name )
except errors . AtrError as e :
print ( f " skipping { name } : { e } " )
else :
2017-05-06 23:49:42 +00:00
for s in parser . segments :
2018-06-04 17:19:08 +00:00
if s . origin > 0 :
2017-05-07 20:28:15 +00:00
print ( " adding %s from %s " % ( s , name ) )
2018-06-04 17:19:08 +00:00
segments . add_segment ( s . data , s . origin )
2017-02-26 20:14:23 +00:00
if options . verbose :
for s in segments :
2018-06-04 17:19:08 +00:00
print ( " %s - %04x ) " % ( str ( s ) [ : - 1 ] , s . origin + len ( s ) ) )
2017-05-02 18:17:32 +00:00
if run_addr :
try :
run_addr = text_to_int ( run_addr )
except ValueError :
run_addr = None
2017-05-10 00:58:16 +00:00
return segments , run_addr
def assemble ( image , source_files , data_files , obj_files , run_addr = " " ) :
segments , run_addr = assemble_segments ( source_files , data_files , obj_files , run_addr )
2017-05-02 18:17:32 +00:00
file_data , filetype = image . create_executable_file_image ( segments , run_addr )
2017-05-07 20:28:15 +00:00
print ( " total file size: $ %x ( %d ) bytes " % ( len ( file_data ) , len ( file_data ) ) )
2017-02-26 20:14:23 +00:00
changed = save_file ( image , options . output , filetype , file_data )
if changed :
image . save ( )
2017-03-23 17:06:37 +00:00
2017-05-10 00:58:16 +00:00
def boot_image ( image_name , source_files , data_files , obj_files , run_addr = " " ) :
2018-03-22 00:10:42 +00:00
try :
image_cls = parsers_for_filename ( image_name ) [ 0 ]
2018-06-24 19:10:59 +00:00
except errors . InvalidDiskImage as e :
2018-03-22 00:10:42 +00:00
print ( " %s : %s " % ( image_name , e ) )
return None
2017-05-10 00:58:16 +00:00
segments , run_addr = assemble_segments ( source_files , data_files , obj_files , run_addr )
2017-07-19 13:47:49 +00:00
if segments :
image = image_cls . create_boot_image ( segments , run_addr )
print ( " saving boot disk %s " % ( image_name ) )
image . save ( image_name )
else :
print ( " No segments to save to boot disk " )
2017-05-10 00:58:16 +00:00
2017-02-26 21:07:36 +00:00
def shred_image ( image , value = 0 ) :
2017-05-07 20:28:15 +00:00
print ( " shredding: free sectors from %s filled with %d " % ( image , value ) )
2017-02-26 21:07:36 +00:00
if not options . dry_run :
image . shred ( )
image . save ( )
2017-02-24 19:57:51 +00:00
2017-05-03 19:25:37 +00:00
def get_template_path ( rel_path = " templates " ) :
path = __file__
template_path = os . path . normpath ( os . path . join ( os . path . dirname ( path ) , rel_path ) )
frozen = getattr ( sys , ' frozen ' , False )
if frozen :
if frozen == True :
# pyinstaller sets frozen=True and uses sys._MEIPASS
root = sys . _MEIPASS
template_path = os . path . normpath ( os . path . join ( root , template_path ) )
elif frozen == ' macosx_app ' :
#print "FROZEN!!! %s" % frozen
root = os . environ [ ' RESOURCEPATH ' ]
if " .zip/ " in template_path :
zippath , template_path = template_path . split ( " .zip/ " )
template_path = os . path . normpath ( os . path . join ( root , template_path ) )
else :
2017-05-07 20:28:15 +00:00
print ( " App packager %s not yet supported for image paths!!! " )
2017-05-03 19:25:37 +00:00
return template_path
2018-03-09 19:28:55 +00:00
def get_template_images ( partial = " " ) :
2017-05-03 19:25:37 +00:00
import glob
path = get_template_path ( )
files = glob . glob ( os . path . join ( path , " * " ) )
2018-03-09 19:28:55 +00:00
templates = { }
for path in files :
name = os . path . basename ( path )
2017-05-04 14:02:09 +00:00
if name . endswith ( " .inf " ) :
continue
2018-03-09 19:28:55 +00:00
if partial not in name :
continue
2017-05-04 14:02:09 +00:00
try :
2018-03-09 19:28:55 +00:00
with open ( path + " .inf " , " r " ) as fh :
2017-05-25 13:50:27 +00:00
s = fh . read ( )
2018-03-09 19:00:09 +00:00
try :
j = json . loads ( s )
except ValueError :
continue
2018-03-09 19:28:55 +00:00
j [ ' name ' ] = name
j [ ' path ' ] = path
templates [ name ] = j
2017-05-04 14:02:09 +00:00
except IOError :
2018-03-09 19:00:09 +00:00
continue
2018-03-09 19:28:55 +00:00
return templates
def get_template_info ( ) :
import textwrap
fmt = " %-14s %s "
templates = get_template_images ( )
lines = [ ]
lines . append ( " available templates: " )
for name in sorted ( templates . keys ( ) ) :
d = textwrap . wrap ( templates [ name ] [ " description " ] , 80 - 1 - 14 - 2 - 2 )
2017-05-06 02:16:45 +00:00
lines . append ( fmt % ( os . path . basename ( name ) , d [ 0 ] ) )
lines . extend ( [ fmt % ( " " , line ) for line in d [ 1 : ] ] )
2017-05-05 04:29:07 +00:00
return os . linesep . join ( lines ) + os . linesep
2017-05-03 19:25:37 +00:00
def get_template_data ( template ) :
2018-03-09 19:28:55 +00:00
possibilities = get_template_images ( template )
if not possibilities :
2018-06-24 19:10:59 +00:00
raise errors . InvalidDiskImage ( " Unknown template disk image %s " % template )
2018-03-09 19:28:55 +00:00
if len ( possibilities ) > 1 :
2018-06-24 19:10:59 +00:00
raise errors . InvalidDiskImage ( " Name %s is ambiguous ( %d matches: %s ) " % ( template , len ( possibilities ) , " , " . join ( sorted ( possibilities . keys ( ) ) ) ) )
2018-03-09 19:28:55 +00:00
name , inf = possibilities . popitem ( )
path = inf [ ' path ' ]
2017-05-03 19:25:37 +00:00
try :
with open ( path , " rb " ) as fh :
data = fh . read ( )
2017-05-04 19:56:16 +00:00
except IOError :
2018-06-24 19:10:59 +00:00
raise errors . InvalidDiskImage ( " Failed reading template file %s " % path )
2017-05-04 19:56:16 +00:00
return data , inf
2017-05-03 19:25:37 +00:00
def create_image ( template , name ) :
2017-05-06 02:16:45 +00:00
import textwrap
2018-03-09 19:06:30 +00:00
try :
data , inf = get_template_data ( template )
2018-06-24 19:10:59 +00:00
except errors . InvalidDiskImage as e :
2018-03-09 19:06:30 +00:00
info = get_template_info ( )
print ( " Error: %s \n \n %s " % ( e , info ) )
return
2018-03-09 19:28:55 +00:00
print ( " Using template %s : \n %s " % ( inf [ ' name ' ] , " \n " . join ( textwrap . wrap ( inf [ " description " ] , 77 ) ) ) )
2017-05-03 19:25:37 +00:00
if not options . dry_run :
if os . path . exists ( name ) and not options . force :
2017-05-07 20:28:15 +00:00
print ( " skipping %s , use -f to overwrite " % ( name ) )
2017-05-03 19:25:37 +00:00
else :
with open ( name , " wb " ) as fh :
fh . write ( data )
parser = find_diskimage ( name )
2017-05-07 20:28:15 +00:00
print ( " created %s : %s " % ( name , str ( parser . image ) ) )
2017-05-03 19:25:37 +00:00
list_files ( parser . image , [ ] )
2017-05-04 19:56:16 +00:00
else :
2017-05-07 20:28:15 +00:00
print ( " creating %s " % name )
2017-05-03 19:25:37 +00:00
2017-05-02 21:18:29 +00:00
2016-02-13 04:36:33 +00:00
def run ( ) :
import argparse
2017-02-24 19:57:51 +00:00
global options
2017-03-23 17:06:37 +00:00
2017-05-02 21:18:29 +00:00
# Subparser command aliasing from: https://gist.github.com/sampsyo/471779
# released into the public domain by its author
class AliasedSubParsersAction ( argparse . _SubParsersAction ) :
class _AliasedPseudoAction ( argparse . Action ) :
def __init__ ( self , name , aliases , help ) :
dest = name
if aliases :
dest + = ' ( %s ) ' % ' , ' . join ( aliases )
sup = super ( AliasedSubParsersAction . _AliasedPseudoAction , self )
sup . __init__ ( option_strings = [ ] , dest = dest , help = help )
def add_parser ( self , name , * * kwargs ) :
if ' aliases ' in kwargs :
aliases = kwargs [ ' aliases ' ]
del kwargs [ ' aliases ' ]
else :
aliases = [ ]
parser = super ( AliasedSubParsersAction , self ) . add_parser ( name , * * kwargs )
# Make the aliases work.
for alias in aliases :
self . _name_parser_map [ alias ] = parser
# Make the help text reflect them, first removing old help entry.
if ' help ' in kwargs :
help = kwargs . pop ( ' help ' )
self . _choices_actions . pop ( )
pseudo_action = self . _AliasedPseudoAction ( name , aliases , help )
self . _choices_actions . append ( pseudo_action )
return parser
command_aliases = {
" list " : [ " t " , " ls " , " dir " , " catalog " ] ,
2017-05-04 17:09:15 +00:00
" crc " : [ ] ,
2017-05-02 21:18:29 +00:00
" extract " : [ " x " ] ,
" add " : [ " a " ] ,
" create " : [ " c " ] ,
2017-05-10 00:58:16 +00:00
" boot " : [ " b " ] ,
2017-05-02 21:18:29 +00:00
" assemble " : [ " s " , " asm " ] ,
" delete " : [ " rm " , " del " ] ,
" vtoc " : [ " v " ] ,
" segments " : [ ] ,
}
2017-05-05 04:29:07 +00:00
# reverse aliases does the inverse mapping of command aliases, including
# the identity mapping of "command" to "command"
2017-05-08 01:58:10 +00:00
reverse_aliases = { z : k for k , v in command_aliases . items ( ) for z in ( v + [ k ] ) }
2017-05-04 17:14:45 +00:00
skip_diskimage_summary = set ( [ " crc " ] )
2017-05-02 21:18:29 +00:00
usage = " %(prog)s [-h] [-v] [--dry-run] DISK_IMAGE [...] "
subparser_usage = " %(prog)s [-h] [-v] [--dry-run] DISK_IMAGE "
parser = argparse . ArgumentParser ( prog = " atrcopy DISK_IMAGE " , description = " Manipulate files on several types of 8-bit computer disk images. Type ' %(prog)s COMMAND --help ' for list of options available for each command. " )
parser . register ( ' action ' , ' parsers ' , AliasedSubParsersAction )
2016-02-13 04:36:33 +00:00
parser . add_argument ( " -v " , " --verbose " , default = 0 , action = " count " )
2017-02-27 20:38:28 +00:00
parser . add_argument ( " --dry-run " , action = " store_true " , default = False , help = " don ' t perform operation, just show what would have happened " )
2017-05-02 21:18:29 +00:00
subparsers = parser . add_subparsers ( dest = ' command ' , help = ' ' , metavar = " COMMAND " )
command = " list "
list_parser = subparsers . add_parser ( command , help = " List files on the disk image. This is the default if no command is specified " , aliases = command_aliases [ command ] )
list_parser . add_argument ( " -g " , " --segments " , action = " store_true " , default = False , help = " display segments " )
list_parser . add_argument ( " -m " , " --metadata " , action = " store_true " , default = False , help = " show extra metadata for named files " )
2017-05-04 17:09:15 +00:00
list_parser . add_argument ( " -c " , " --crc " , action = " store_true " , default = False , help = " compute CRC32 for each file " )
2017-05-02 21:18:29 +00:00
list_parser . add_argument ( " files " , metavar = " FILENAME " , nargs = " * " , help = " an optional list of files to display " )
2017-05-04 17:09:15 +00:00
command = " crc "
crc_parser = subparsers . add_parser ( command , help = " List files on the disk image and the CRC32 value in format suitable for parsing " , aliases = command_aliases [ command ] )
crc_parser . add_argument ( " files " , metavar = " FILENAME " , nargs = " * " , help = " an optional list of files to display " )
2017-05-02 21:18:29 +00:00
command = " extract "
extract_parser = subparsers . add_parser ( command , help = " Copy files from the disk image to the local filesystem " , aliases = command_aliases [ command ] )
extract_parser . add_argument ( " -a " , " --all " , action = " store_true " , default = False , help = " operate on all files on disk image " )
extract_parser . add_argument ( " -l " , " --lower " , action = " store_true " , default = False , help = " convert extracted filenames to lower case " )
#extract_parser.add_argument("-n", "--no-sys", action="store_true", default=False, help="only extract things that look like games (no DOS or .SYS files)")
extract_parser . add_argument ( " -e " , " --ext " , action = " store " , nargs = 1 , default = False , help = " add the specified extension " )
extract_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " allow file overwrites on local filesystem " )
extract_parser . add_argument ( " files " , metavar = " FILENAME " , nargs = " * " , help = " if not using the -a/--all option, a file (or list of files) to extract from the disk image. " )
command = " add "
add_parser = subparsers . add_parser ( command , help = " Add files to the disk image " , aliases = command_aliases [ command ] )
add_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " allow file overwrites in the disk image " )
add_parser . add_argument ( " -t " , " --filetype " , action = " store " , default = " " , help = " file type metadata for writing to disk images that require it (e.g. DOS 3.3) " )
add_parser . add_argument ( " files " , metavar = " FILENAME " , nargs = " + " , help = " a file (or list of files) to copy to the disk image " )
2017-05-03 19:25:37 +00:00
command = " create "
2017-05-05 04:29:07 +00:00
create_parser = subparsers . add_parser ( command , help = " Create a new disk image " , aliases = command_aliases [ command ] , epilog = " <generated on demand to list available templates> " , formatter_class = argparse . RawDescriptionHelpFormatter )
2017-05-03 19:25:37 +00:00
create_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " replace disk image file if it exists " )
2017-05-05 04:29:07 +00:00
create_parser . add_argument ( " template " , metavar = " TEMPLATE " , nargs = 1 , help = " template to use to create new disk image; see below for list of available built-in templates " )
2017-05-02 21:18:29 +00:00
command = " assemble "
assembly_parser = subparsers . add_parser ( command , help = " Create a new binary file in the disk image " , aliases = command_aliases [ command ] )
assembly_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " allow file overwrites in the disk image " )
assembly_parser . add_argument ( " -s " , " --asm " , nargs = " * " , action = " append " , help = " source file(s) to assemble using pyatasm " )
2017-05-06 23:49:42 +00:00
assembly_parser . add_argument ( " -d " , " --data " , nargs = " * " , action = " append " , help = " binary data file(s) to add to assembly, specify as file@addr. Only a portion of the file may be included; specify the subset using standard python slice notation: file[subset]@addr " )
assembly_parser . add_argument ( " -b " , " --obj " , " --bload " , nargs = " * " , action = " append " , help = " binary file(s) to add to assembly, either executables or labeled memory dumps (e.g. BSAVE on Apple ][), parsing each file ' s binary segments to add to the resulting disk image at the load address for each segment " )
2017-05-02 21:18:29 +00:00
assembly_parser . add_argument ( " -r " , " --run-addr " , " --brun " , action = " store " , default = " " , help = " run address of binary file if not the first byte of the first segment " )
assembly_parser . add_argument ( " -o " , " --output " , action = " store " , default = " " , required = True , help = " output file name in disk image " )
2017-05-10 00:58:16 +00:00
command = " boot "
boot_parser = subparsers . add_parser ( command , help = " Create a bootable disk image " , aliases = command_aliases [ command ] )
boot_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " allow file overwrites in the disk image " )
boot_parser . add_argument ( " -s " , " --asm " , nargs = " * " , action = " append " , help = " source file(s) to assemble using pyatasm " )
boot_parser . add_argument ( " -d " , " --data " , nargs = " * " , action = " append " , help = " binary data file(s) to add to assembly, specify as file@addr. Only a portion of the file may be included; specify the subset using standard python slice notation: file[subset]@addr " )
boot_parser . add_argument ( " -b " , " --obj " , " --bload " , nargs = " * " , action = " append " , help = " binary file(s) to add to assembly, either executables or labeled memory dumps (e.g. BSAVE on Apple ][), parsing each file ' s binary segments to add to the resulting disk image at the load address for each segment " )
boot_parser . add_argument ( " -r " , " --run-addr " , " --brun " , action = " store " , default = " " , help = " run address of binary file if not the first byte of the first segment " )
2017-05-02 21:18:29 +00:00
command = " delete "
delete_parser = subparsers . add_parser ( command , help = " Delete files from the disk image " , aliases = command_aliases [ command ] )
delete_parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " remove the file even if it is write protected ( ' locked ' in Atari DOS 2 terms), if write-protect is supported disk image " )
delete_parser . add_argument ( " files " , metavar = " FILENAME " , nargs = " + " , help = " a file (or list of files) to remove from the disk image " )
command = " vtoc "
vtoc_parser = subparsers . add_parser ( command , help = " Show a formatted display of sectors free in the disk image " , aliases = command_aliases [ command ] )
vtoc_parser . add_argument ( " -e " , " --clear-empty " , action = " store_true " , default = False , help = " fill empty sectors with 0 " )
command = " segments "
vtoc_parser = subparsers . add_parser ( command , help = " Show the list of parsed segments in the disk image " , aliases = command_aliases [ command ] )
# argparse doesn't seem to allow an argument fixed to item 1, so have to
# hack with the arg list to get arg #1 to be the disk image. Because of
# this hack, we have to perform an additional hack to figure out what the
# --help option applies to if it's in the argument list.
args = list ( sys . argv [ 1 : ] )
if len ( args ) > 0 :
found_help = - 1
first_non_dash = 0
num_non_dash = 0
2017-05-03 04:26:28 +00:00
non_dash = [ ]
for i , arg in enumerate ( args ) :
if arg . startswith ( " - " ) :
2017-05-02 21:18:29 +00:00
if i == 0 :
first_non_dash = - 1
2017-05-03 04:26:28 +00:00
if arg == " -h " or arg == " --help " :
2017-05-02 21:18:29 +00:00
found_help = i
else :
num_non_dash + = 1
2017-05-03 04:26:28 +00:00
non_dash . append ( arg )
2017-05-02 21:18:29 +00:00
if first_non_dash < 0 :
first_non_dash = i
if found_help > = 0 or first_non_dash < 0 :
if found_help == 0 or first_non_dash < 0 :
# put dummy argument so help for entire script will be shown
args = [ " --help " ]
2017-05-05 04:29:07 +00:00
elif non_dash [ 0 ] in reverse_aliases :
2017-05-02 21:18:29 +00:00
# if the first argument without a leading dash looks like a
# command instead of a disk image, show help for that command
2017-05-03 04:26:28 +00:00
args = [ non_dash [ 0 ] , " --help " ]
2017-05-05 04:29:07 +00:00
elif len ( non_dash ) > 0 and non_dash [ 1 ] in reverse_aliases :
2017-05-03 04:26:28 +00:00
# if the first argument without a leading dash looks like a
# command instead of a disk image, show help for that command
args = [ non_dash [ 1 ] , " --help " ]
2017-05-02 21:18:29 +00:00
else :
# show script help
args = [ " --help " ]
2017-05-05 17:59:28 +00:00
if reverse_aliases . get ( args [ 0 ] , None ) == " create " :
2017-05-05 04:29:07 +00:00
create_parser . epilog = get_template_info ( )
2017-05-02 21:18:29 +00:00
else :
2017-05-05 04:29:07 +00:00
# Allow global options to come before or after disk image name
disk_image_name = args [ first_non_dash ]
args [ first_non_dash : first_non_dash + 1 ] = [ ]
if num_non_dash == 1 :
# If there is only a disk image but no command specified,
# use the default
args . append ( ' list ' )
2017-05-02 21:18:29 +00:00
else :
disk_image_name = None
2018-03-22 19:25:29 +00:00
parser . print_help ( )
sys . exit ( 1 )
2017-05-02 21:18:29 +00:00
# print "parsing: %s" % str(args)
options = parser . parse_args ( args )
# print options
2017-05-05 04:29:07 +00:00
command = reverse_aliases [ options . command ]
2016-02-13 04:36:33 +00:00
2016-04-13 00:13:33 +00:00
# Turn off debug messages by default
2017-02-25 06:20:07 +00:00
logging . basicConfig ( level = logging . WARNING )
2016-04-13 00:13:33 +00:00
log = logging . getLogger ( " atrcopy " )
if options . verbose :
log . setLevel ( logging . DEBUG )
else :
log . setLevel ( logging . INFO )
2017-03-23 17:06:37 +00:00
2017-05-02 21:18:29 +00:00
if command == " create " :
2017-05-05 20:33:09 +00:00
create_image ( options . template [ 0 ] , disk_image_name )
2017-05-10 00:58:16 +00:00
elif command == " boot " :
asm = options . asm [ 0 ] if options . asm else [ ]
data = options . data [ 0 ] if options . data else [ ]
obj = options . obj [ 0 ] if options . obj else [ ]
boot_image ( disk_image_name , asm , data , obj , options . run_addr )
2017-05-02 21:18:29 +00:00
else :
2018-06-25 18:35:38 +00:00
try :
parser = find_diskimage ( disk_image_name )
except ( errors . UnsupportedContainer , errors . UnsupportedDiskImage , IOError ) as e :
print ( f " { disk_image_name } : { e } " )
else :
2017-05-04 17:14:45 +00:00
if command not in skip_diskimage_summary :
2017-05-07 20:28:15 +00:00
print ( " %s : %s " % ( disk_image_name , parser . image ) )
2017-05-02 21:18:29 +00:00
if command == " vtoc " :
2017-02-26 22:14:25 +00:00
vtoc = parser . image . get_vtoc_object ( )
2017-05-07 20:28:15 +00:00
print ( vtoc )
2017-05-02 21:18:29 +00:00
if options . clear_empty :
shred_image ( parser . image )
elif command == " list " :
2017-05-04 19:44:44 +00:00
list_files ( parser . image , options . files , options . crc , options . metadata )
2017-05-04 17:09:15 +00:00
elif command == " crc " :
crc_files ( parser . image , options . files )
2017-05-02 21:18:29 +00:00
elif command == " add " :
add_files ( parser . image , options . files )
elif command == " delete " :
remove_files ( parser . image , options . files )
elif command == " extract " :
extract_files ( parser . image , options . files )
elif command == " assemble " :
asm = options . asm [ 0 ] if options . asm else [ ]
data = options . data [ 0 ] if options . data else [ ]
2017-05-06 23:49:42 +00:00
obj = options . obj [ 0 ] if options . obj else [ ]
assemble ( parser . image , asm , data , obj , options . run_addr )
2017-05-02 21:18:29 +00:00
elif command == " segments " :
2017-05-07 20:28:15 +00:00
print ( " \n " . join ( [ str ( a ) for a in parser . segments ] ) )