2017-02-25 06:05:41 +00:00
__version__ = " 4.0.0 "
2016-04-13 00:13:33 +00:00
import logging
2016-02-13 04:36:33 +00:00
try :
import numpy as np
except ImportError :
raise RuntimeError ( " atrcopy %s requires numpy " % __version__ )
from errors import *
2017-03-21 23:25:59 +00:00
from ataridos import AtrHeader , AtariDosDiskImage , BootDiskImage , AtariDosFile , XexContainerSegment , get_xex , add_atr_header
2017-02-22 20:11:28 +00:00
from dos33 import Dos33DiskImage
2016-05-06 00:44:20 +00:00
from kboot import KBootImage , add_xexboot_header
2017-03-21 23:25:59 +00:00
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
2016-02-13 04:36:33 +00:00
from spartados import SpartaDosDiskImage
2016-06-03 20:05:16 +00:00
from cartridge import A8CartHeader , AtariCartImage
2016-06-03 19:40:48 +00:00
from parsers import SegmentParser , DefaultSegmentParser , guess_parser_for_mime , guess_parser_for_system , iter_parsers , iter_known_segment_parsers , mime_parse_order
2017-02-26 20:31:34 +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 :
print " %s : %s %s " % ( dirent , action , outfilename )
if not skip :
bytes = image . get_file ( dirent )
with open ( outfilename , " wb " ) as fh :
fh . write ( bytes )
else :
print dirent
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
def find_diskimage ( filename ) :
2017-03-07 18:43:46 +00:00
try :
with open ( filename , " rb " ) as fh :
2017-02-24 19:57:51 +00:00
if options . verbose :
2017-03-07 18:43:46 +00:00
print " Loading file %s " % filename
rawdata = SegmentData ( fh . read ( ) )
parser = None
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
print " %s : %s " % ( filename , parser . image )
break
2017-02-24 19:57:51 +00:00
if parser is None :
2017-03-07 18:43:46 +00:00
print " %s : Unknown disk image type " % filename
except UnsupportedDiskImage , e :
print " %s : %s " % ( filename , e )
return None
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 ) :
for name in files :
try :
dirent = image . find_dirent ( name )
except FileNotFound :
print " %s not in %s " % ( name , image )
continue
print " extracting %s " % name
if not options . dry_run :
data = image . get_file ( dirent )
2017-02-26 22:10:01 +00:00
with open ( dirent . filename , " wb " ) as fh :
2017-02-24 19:57:51 +00:00
fh . write ( data )
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 :
print " skipping %s , use -f to overwrite " % ( name )
return False
except FileNotFound :
pass
print " copying %s to %s " % ( name , image )
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 )
except FileNotFound :
print " %s not in %s " % ( name , image )
continue
print " removing %s from %s " % ( name , image )
if not options . dry_run :
image . delete_file ( name )
changed = True
if changed :
image . save ( )
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
def list_files ( image , files ) :
files = set ( files )
for dirent in image . files :
if not files or dirent . filename in files :
print dirent
2017-02-27 01:08:55 +00:00
if options . metadata :
print dirent . extra_metadata ( image )
2017-02-24 19:57:51 +00:00
2017-03-23 17:06:37 +00:00
2017-02-26 20:31:34 +00:00
def assemble ( image , source_files , data_files ) :
if source_files :
try :
import pyatasm
except ImportError :
raise 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 )
except SyntaxError , e :
raise AtrError ( " Assembly error: %s " % e . msg )
for first , last , object_code in asm . segments :
2017-02-26 20:31:34 +00:00
s = segments . add_segment ( object_code , first )
print s . name
for name in data_files :
if " @ " not in name :
raise AtrError ( " Data files must include a load address specified with the @ char " )
name , addr = name . rsplit ( " @ " , 1 )
first = text_to_int ( addr )
2017-05-01 15:41:23 +00:00
subset = slice ( 0 , - 1 )
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 )
print s . name
2017-02-26 20:14:23 +00:00
if options . verbose :
for s in segments :
print " %s - %04x ) " % ( str ( s ) [ : - 1 ] , s . start_addr + len ( s ) )
file_data , filetype = image . create_executable_file_image ( segments )
changed = save_file ( image , options . output , filetype , file_data )
if changed :
image . save ( )
2017-03-23 17:06:37 +00:00
2017-02-26 21:07:36 +00:00
def shred_image ( image , value = 0 ) :
print " shredding: free sectors from %s filled with %d " % ( image , value )
if not options . dry_run :
image . shred ( )
image . save ( )
2017-02-24 19:57:51 +00:00
2016-02-13 04:36:33 +00:00
def run ( ) :
import sys
import argparse
2017-02-24 19:57:51 +00:00
global options
2017-03-23 17:06:37 +00:00
2017-02-24 19:57:51 +00:00
parser = argparse . ArgumentParser ( description = " Manipulate files on several types of 8-bit computer disk images " )
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-02-24 19:57:51 +00:00
parser . add_argument ( " -x " , " -e " , " --extract " , action = " store_true " , default = False , help = " extract named files " )
parser . add_argument ( " -a " , " --add " , action = " store_true " , default = False , help = " add files to image " )
2017-02-27 19:56:08 +00:00
parser . add_argument ( " -d " , " --delete " , action = " store_true " , default = False , help = " remove named files from image " )
2017-02-24 19:57:51 +00:00
parser . add_argument ( " -t " , " --filetype " , action = " store " , default = " " , help = " file type metadata for writing to disk images that require it " )
2017-02-26 20:14:23 +00:00
parser . add_argument ( " -s " , " --asm " , nargs = " + " , action = " append " , help = " source file(s) to assemble using pyatasm (requires -o to specify filename stored on disk image) " )
2017-02-26 20:31:34 +00:00
parser . add_argument ( " -b " , " --bytes " , nargs = " + " , action = " append " , help = " data file(s) to add to assembly, specify as file@addr (requires -o to specify filename stored on disk image) " )
2017-02-26 20:14:23 +00:00
parser . add_argument ( " -o " , " --output " , action = " store " , default = " " , help = " output file name for those commands that need it " )
2017-02-27 20:38:28 +00:00
parser . add_argument ( " -f " , " --force " , action = " store_true " , default = False , help = " force operation, allowing file overwrites or attempt operation on non-standard disk images " )
parser . add_argument ( " --all " , action = " store_true " , default = False , help = " operate on all files on disk image " )
parser . add_argument ( " -l " , " --lower " , action = " store_true " , default = False , help = " convert extracted filenames to lower case " )
parser . add_argument ( " -n " , " --no-sys " , action = " store_true " , default = False , help = " only extract things that look like games (no DOS or .SYS files) " )
parser . add_argument ( " --xex " , action = " store_true " , default = False , help = " add .xex extension " )
parser . add_argument ( " -g " , " --segments " , action = " store_true " , default = False , help = " display segments " )
2017-02-26 21:07:36 +00:00
parser . add_argument ( " --shred " , action = " store_true " , default = False , help = " fill empty sectors with 0 " )
2017-02-26 22:14:25 +00:00
parser . add_argument ( " --vtoc " , action = " store_true " , default = False , help = " show the VTOC " )
2017-02-27 01:08:55 +00:00
parser . add_argument ( " -m " , " --metadata " , action = " store_true " , default = False , help = " show extra metadata for named files " )
2017-02-27 20:38:28 +00:00
parser . add_argument ( " files " , metavar = " IMAGE " , nargs = " + " , help = " a disk image file [or a list of them] " )
2016-02-13 04:36:33 +00:00
options , extra_args = parser . parse_known_args ( )
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-02-24 19:57:51 +00:00
file_list = [ ]
2017-02-27 19:56:08 +00:00
if options . add or options . extract or options . delete :
2017-02-24 19:57:51 +00:00
image = options . files . pop ( )
file_list = options . files
options . files = [ image ]
2017-02-26 22:10:01 +00:00
if options . all and file_list :
raise AtrError ( " Specifying a list of files and --all doesn ' t make sense. " )
2017-03-07 18:43:46 +00:00
image_files = [ ]
2016-02-13 04:36:33 +00:00
for filename in options . files :
2017-03-07 18:43:46 +00:00
if filename == " - " :
import fileinput
for line in fileinput . input ( [ " - " ] ) :
line = line . rstrip ( )
print " --> %s <-- " % line
image_files . append ( line )
else :
image_files . append ( filename )
for filename in image_files :
2017-02-24 19:57:51 +00:00
parser = find_diskimage ( filename )
if parser and parser . image :
if options . all :
2017-02-26 22:10:01 +00:00
file_list = list ( parser . image . files )
if options . segments :
2017-02-24 19:57:51 +00:00
print " \n " . join ( [ str ( a ) for a in parser . segments ] )
elif options . add :
add_files ( parser . image , file_list )
elif options . extract :
extract_files ( parser . image , file_list )
2017-02-27 19:56:08 +00:00
elif options . delete :
2017-02-24 19:57:51 +00:00
remove_files ( parser . image , file_list )
2017-02-26 20:31:34 +00:00
elif options . asm or options . bytes :
asm = options . asm [ 0 ] if options . asm else [ ]
datafiles = options . bytes [ 0 ] if options . bytes else [ ]
assemble ( parser . image , asm , datafiles )
2017-02-24 19:57:51 +00:00
else :
list_files ( parser . image , file_list )
2017-02-26 21:07:36 +00:00
if options . shred :
shred_image ( parser . image )
2017-02-26 22:14:25 +00:00
if options . vtoc :
vtoc = parser . image . get_vtoc_object ( )
print vtoc