From 870836d6cfce9c7005e035f7046afd6d460fbbdd Mon Sep 17 00:00:00 2001 From: robjustice Date: Fri, 14 Dec 2018 15:13:48 +1100 Subject: [PATCH] Add files via upload --- A3Driverutil.py | 626 ++++++++++++++++++++++++++++++++++++++++++++++++ Apple3_o65.cfg | 21 ++ 2 files changed, 647 insertions(+) create mode 100644 A3Driverutil.py create mode 100644 Apple3_o65.cfg diff --git a/A3Driverutil.py b/A3Driverutil.py new file mode 100644 index 0000000..3c291f4 --- /dev/null +++ b/A3Driverutil.py @@ -0,0 +1,626 @@ +#!/usr/bin/python +# + +# use unpack from struct and argv from sys +from struct import unpack,pack; import argparse; + +parser = argparse.ArgumentParser( + prog='A3Driverutil.py', + formatter_class=argparse.RawDescriptionHelpFormatter, + description='''\ +Driverutil.py - A Python script to work with A3 drivers + By Robert Justice + +The primary function is to convert an o65 relocatable 6502 binary +file to A3 driver format. This is to allow driver development using +the ca65 assembler + +Support to add, update the converted driver into a SOS.DRIVER file +and delete & list is also included. + +Finally, has some extract functions to extract driver code to allow +disassembly. One of these will relocate the extracted Driver to +0x2000 base address to minimise any confusion with zero page when +disassembling. + + https://github.com/rob_justice/A3Driverutil + + reuses some functions and ideas from Driv3rs.py, thank you + https://github.com/thecompu/Driv3rs + +''') + +subparsers = parser.add_subparsers(help='Command Description',dest="command") + +# Bin command +bin_parser = subparsers.add_parser( + 'bin', help='Convert o65 binary to A3 driver binary format, Comment + Code + RelocationTable') +bin_parser.add_argument( + 'o65file', action='store', + help='Input o65 code file to be converted') +bin_parser.add_argument( + 'binfile', action='store', + help='Binary output file') + +# Sos command +sos_parser = subparsers.add_parser( + 'sos', help='Convert o65 binary and output as SOS.DRIVER format(for use with scp)') +sos_parser.add_argument( + 'o65file', action='store', + help='Input o65 code file to be converted') +sos_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER Binary output file') + +# List command +add_parser = subparsers.add_parser( + 'list', help='List current drivers in a SOS.DRIVER file') +add_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to be updated') + +# Add command +add_parser = subparsers.add_parser( + 'add', help='Convert o65 binary and add as new driver to a existing SOS.DRIVER file') +add_parser.add_argument( + 'o65file', action='store', + help='Input o65 code file to be converted') +add_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to list the contained drivers') + +# Update command +update_parser = subparsers.add_parser( + 'update', help='Convert o65 binary and update existing driver in a SOS.DRIVER file') +update_parser.add_argument( + 'o65file', action='store', + help='Input o65 code file to be converted') +update_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to be updated') + +# Delete command +delete_parser = subparsers.add_parser( + 'delete', help='Delete a driver from an existing SOS.DRIVER file') +delete_parser.add_argument( + 'drivername', action='store', + help='Name of driver to be deleted (include . eg: ".console"') +delete_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to delete the driver from') + +# Extract command +extract_parser = subparsers.add_parser( + 'extract', help='Extract a driver from an existing SOS.DRIVER file') +extract_parser.add_argument( + 'drivername', action='store', + help='Name of driver to be extracted (include . eg: ".console"') +extract_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to extract the driver from') + +# Extract code and relocate to 0x2000 command +extract_parser = subparsers.add_parser( + 'extractcode', help='Extract a drivers code from an existing SOS.DRIVER file and relocate to 0x2000 to aid disassembly') +extract_parser.add_argument( + 'drivername', action='store', + help='Name of driver to be extracted (include . eg: ".console"') +extract_parser.add_argument( + 'sosfile', action='store', + help='SOS.DRIVER file to extract the driver from') + +args = parser.parse_args() + + +# this function unpacks several read operations -- +# text, binary, and single-byte. Each uses unpack from +# struct and attempts converts the resulting tuple into +# into either a string or integer, depending upon need. +def readUnpack(file,bytes, **options): + if options.get("type") == 't': + SOS = file.read(bytes) + text_unpacked = unpack('%ss' % bytes, SOS) + return ''.join(text_unpacked) + + if options.get("type") == 'b': + SOS = file.read(bytes) + offset_unpacked = unpack ('< H', SOS) + return int('.'.join(str(x) for x in offset_unpacked)) + + if options.get("type") == '1': + SOS = file.read(bytes) + offset_unpacked = unpack ('< B', SOS) + return int(ord(SOS)) + +# this function reads a word from a string at the specified +# offset and returns an integer +def readWord(data,startpos): + return ord(data[startpos+1])*256+ord(data[startpos]) + +# this function reads a byte from a string at the specified +# offset and returns an integer +def readByte(data,startpos): + return ord(data[startpos]) + +# +# this function reads in a o65 binary file of a driver and +# converts to the same format as contained in the SOS.DRIVER +# file for drivers, Comment, Code, RelocateTable +# input - filename of o65 file +# returns - converted driver as a string +# +def convert_o65(file): + o65file = open(file, 'rb') + + #parse the o65 file + byte = readUnpack(o65file,1,type = '1') #non-C64 marker, 2 bytes + byte = readUnpack(o65file,1,type = '1') + o65 = readUnpack(o65file,3,type = 't') # "o65" MAGIC number! + if o65 == 'o65': #valid file, lets keep going + version = readUnpack(o65file,1,type = '1') # version + mode = readUnpack(o65file,2,type = 'b') # mode word + tbase = readUnpack(o65file,2,type = 'b') # address to which text is assembled to originally + tlen = readUnpack(o65file,2,type = 'b') # length of text segment + dbase = readUnpack(o65file,2,type = 'b') # originating address for data segment + dlen = readUnpack(o65file,2,type = 'b') # length of data segment + bbase = readUnpack(o65file,2,type = 'b') # originating address for bss segment + blen = readUnpack(o65file,2,type = 'b') # length of bss segment + zbase = readUnpack(o65file,2,type = 'b') # originating address for zero segment + zlen = readUnpack(o65file,2,type = 'b') # length of zero segment + stack = readUnpack(o65file,2,type = 'b') # minimum needed stack size, 0= not known. + + #print ("mode: ",mode) + #print ("tbase: ",tbase) + #print ("tlen: ",tlen) + #print ("dbase: ",dbase) + #print ("dlen: ",dlen) + + #skip over header options + olen = readUnpack(o65file,1,type = '1') + while olen != 0 : #0 marks end of options header + otype = readUnpack(o65file,1,type = '1') + option_bytes = readUnpack(o65file,olen-2,type = 't') + olen = readUnpack(o65file,1,type = '1') + + driver='' #this will be the converted driver + + #add text segment + driver += o65file.read(tlen) #this is the comment part + + #trim off the comment 0xFFFF if there is a comment + if readWord(driver,0) == 0xFFFF: + driver = driver[2:] + + #add data segment length + driver += pack(' 254, so add this and get next byte + offset_address = offset_address + offset -1 #add 254 + offset = readUnpack(o65file,1,type = '1') + else: + typebyte = readUnpack(o65file,1,type = '1') + if typebyte == 0x83: #8=word offset and 3=data segment + offset_address = offset_address + offset + reloctable.append(offset_address) + offset = readUnpack(o65file,1,type = '1') + + #add the length of the relocation table + driver += pack('H',0x0400) #Number of Disk /// drives installed (4) + header += '?? ' #char set name, ?? indicates no char set included (16 chars long) + + for i in range(0,0x400): #pad out the char set with spaces + header += ' ' + + header += '?? ' #keyboard layout name (16 chars long) + + for i in range(0,0x100): #pad out with spaces + header += ' ' + + sos_file = args.sosfile + outfile = open(sos_file,'wb') + outfile.write(header + driver + pack('>H',0xFFFF)) #add the end marker + + print 'File converted and written as SOS.DRIVER binary file to:',sos_file + outfile.close() + +#Convert and add to an existing SOS.DRIVER file +elif args.command == 'add': + driver = convert_o65(args.o65file) #convert the driver code + + driver_name = getDriverName(driver) #extract the driver name from the driver + + sos_file = args.sosfile #read in the existing SOS.DRIVER file + sosdriver = open(sos_file,'rb') + sosdriverfile = sosdriver.read() + sosdriver.close() + + drivers_list = parsedriverfile(sosdriverfile)[0] #we just want the first item in the returned list + driver_end = parsedriverfile(sosdriverfile)[1] #this is the offset of the 0xFFFF end marker + + #lets check if it already exists in the SOS.DRIVER file + driver_details = [] + + for i in range(0,len(drivers_list)): + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(sosdriverfile,offset,0)) #we always use dib0 + + i = find(driver_details,'name',driver_name.upper()) #find index of the driver to add, convert name to uppercase + + if i == -1: + #not found, lets add + trimmed_sosdriver = sosdriverfile[0:driver_end] #trim of the 0xFFFF end marker + newsosdriverfile = trimmed_sosdriver + driver + chr(0xFF) + chr(0xFF) + + sosdriver = open(sos_file,'wb') #write it back out, overwriting the old one + sosdriver.write(newsosdriverfile) + sosdriver.close() + + print 'Driver: ' + driver_name + ' added to ' + sos_file + + else: + #found, report error + print 'Driver: ' + driver_name + ' elready exists in ' + sos_file + ', not added' + + +#List drivers in a SOS.DRIVER file +elif args.command == 'list': + sos_file = args.sosfile + sosdriver = open(sos_file,'rb') + filedata = sosdriver.read() + drivers_list = parsedriverfile(filedata)[0] #we just want the first item in the returned list + + driver_details = [] + + for i in range(0,len(drivers_list)): + dib = 0 + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(filedata,offset,dib)) + nextdib = readWord(filedata,offset+2) #next dib of this driver + while nextdib != 0: + dib += 1 + driver_details.append(parseDIB(filedata,offset+nextdib,dib)) + nextdib = readWord(filedata,offset+nextdib+2) #next dib of this driver + + print 'DriverName Status Slot Unit Manid Release' + for i in range(0,len(driver_details)): + #decode status byte + if driver_details[i]['status'] & 0x80 == 0x80: + status = 'active' + else: + status = 'inactive' + #decode slot + if driver_details[i]['slot'] == 0: + slot = 'N/A' + else: + slot = driver_details[i]['slot'] + + if driver_details[i]['dib_num'] == 0: #don't indent the first DIB + print '{:16} {:10} {:3} {:02X} {:04X} {:04X}'.format(driver_details[i]['name'], status, slot, driver_details[i]['unit'],driver_details[i]['manid'],driver_details[i]['release']) + else: #otherwise indent the rest, ie sub devices + print ' {:16}{:10} {:3} {:02X} {:04X} {:04X}'.format(driver_details[i]['name'], status, slot, driver_details[i]['unit'],driver_details[i]['manid'],driver_details[i]['release']) + + print '\n Total size: ',len(filedata) + + +#Convert and update an existing driver in a SOS.DRIVER file +elif args.command == 'update': + driver = convert_o65(args.o65file) #convert the driver code + + driver_name = getDriverName(driver) #extract the driver name from the driver + + print 'Driver in o65 file: ',driver_name + + sos_file = args.sosfile #read in the existing SOS.DRIVER file + sosdriver = open(sos_file,'rb') + sosdriverfile = sosdriver.read() + sosdriver.close() + + drivers_list = parsedriverfile(sosdriverfile)[0] + drivers_end = parsedriverfile(sosdriverfile)[1] + + driver_details = [] + + for i in range(0,len(drivers_list)): + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(sosdriverfile,offset,0)) #we always use dib0 + + i = find(driver_details,'name',driver_name.upper()) #find index of the driver to update, convert name to uppercase + + if i != -1: + #found it + print 'Driver found in SOS.DRIVER, updating..' + #print drivers_list + + newsosdriverfile = sosdriverfile[0:drivers_list[i]['comment_start']] #part up to target driver + newsosdriverfile += driver #add the updated driver + + if i < len(drivers_list)-1: #check if its not the last one + newsosdriverfile += sosdriverfile[drivers_list[i+1]['comment_start']:] #add the rest after the target driver + else: #otherwise we use the end marker + newsosdriverfile += sosdriverfile[drivers_end:] #add the rest after the target driver + + sosdriver = open(sos_file,'wb') #write it back out + sosdriver.write(newsosdriverfile) + sosdriver.close() + + print 'Driver: ' + driver_name + ' updated!' + + else: + #not found + print 'Driver: ' + driver_name + ' not found in SOS.DRIVER file' + + +#Delete an existing driver in a SOS.DRIVER file +elif args.command == 'delete': + driver_name = args.drivername + + sos_file = args.sosfile #read in the SOS.DRIVER file + sosdriver = open(sos_file,'rb') + sosdriverfile = sosdriver.read() + sosdriver.close() + + drivers_list = parsedriverfile(sosdriverfile)[0] + drivers_end = parsedriverfile(sosdriverfile)[1] + + driver_details = [] + + for i in range(0,len(drivers_list)): + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(sosdriverfile,offset,0)) #we always use dib0 + + i = find(driver_details,'name',driver_name.upper()) #find index of the driver to delete, convert name to uppercase + + if i != -1: + #found it + print 'Driver found in SOS.DRIVER, deleting..' + + newsosdriverfile = sosdriverfile[0:drivers_list[i]['comment_start']] #part up to target driver + + if i < len(drivers_list)-1: #check if its not the last one + newsosdriverfile += sosdriverfile[drivers_list[i+1]['comment_start']:] #add the rest after the target driver + else: #otherwise we use the end marker + newsosdriverfile += sosdriverfile[drivers_end:] #add the rest after the target driver + + sosdriver = open(sos_file,'wb') #write it back out + sosdriver.write(newsosdriverfile) + sosdriver.close() + + print 'Driver: ' + driver_name + ' deleted!' + + else: + #not found + print 'Driver: ' + driver_name + ' not found in SOS.DRIVER file' + + + +#Extract a driver from a SOS.DRIVER file +elif args.command == 'extract': + driver_name = args.drivername + + sos_file = args.sosfile #read in the SOS.DRIVER file + sosdriver = open(sos_file,'rb') + sosdriverfile = sosdriver.read() + sosdriver.close() + + drivers_list = parsedriverfile(sosdriverfile)[0] #parse the driver file to find the positions of the drivers + drivers_end = parsedriverfile(sosdriverfile)[1] #parse the driver file to find the end of the drivers + + driver_details = [] #now grab the details from dib0 of each them ie to find the names + for i in range(0,len(drivers_list)): + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(sosdriverfile,offset,0)) #we always use dib0 + + i = find(driver_details,'name',driver_name.upper()) #find index of the driver to extract, convert name to uppercase + + if i != -1: + #found it + print 'Driver found in SOS.DRIVER, extracting..' + + if i < len(drivers_list)-1: #check if its not the last one in sos.driver + extracted_driver = sosdriverfile[drivers_list[i]['comment_start']:drivers_list[i+1]['comment_start']] + + else: #must be the last one, so we use the offset of the 0xFFFF marker + extracted_driver = sosdriverfile[drivers_list[i]['comment_start']:drivers_end] + + filename = driver_name[1:] + '.driver' #chop off the ., and add .driver to the end + driverfile = open(filename,'wb') #write the new driver out + driverfile.write(extracted_driver) + driverfile.close() + + print 'Driver: ' + driver_name + ' extracted and written to file: ' + filename + + else: + #not found + print 'Driver: ' + driver_name + ' not found in SOS.DRIVER file' + +#Extract a drivers code from a SOS.DRIVER file and relocate to 0x2000 to aid disassembly +# relocating to something other than 0x0000 helps to remove zero page ambiguities when +# disassembling the driver code +elif args.command == 'extractcode': + driver_name = args.drivername + + sos_file = args.sosfile #read in the SOS.DRIVER file + sosdriver = open(sos_file,'rb') + sosdriverfile = sosdriver.read() + sosdriver.close() + + drivers_list = parsedriverfile(sosdriverfile)[0] #parse the driver file to find the positions of the drivers + drivers_end = parsedriverfile(sosdriverfile)[1] #parse the driver file to find the end of the drivers + + driver_details = [] #now grab the details from dib0 of each them ie to find the names + for i in range(0,len(drivers_list)): + offset = drivers_list[i]['code_start'] + driver_details.append(parseDIB(sosdriverfile,offset,0)) #we always use dib0 + + i = find(driver_details,'name',driver_name.upper()) #find index of the driver to extract, convert name to uppercase + + if i != -1: + #found it + print 'Driver found in SOS.DRIVER, extracting code..' + + #grab the code + extracted_driver_code = sosdriverfile[drivers_list[i]['code_start']+2:drivers_list[i]['reloc_start']] #skip the code length(+2) + + #grab the relocate table + if i < len(drivers_list)-1: #check if its not the last one in sos.driver + extracted_driver_reloc = sosdriverfile[drivers_list[i]['reloc_start']+2:drivers_list[i+1]['comment_start']] #skip the reloc length(+2) + else: #must be the last one, so we use the offset of the 0xFFFF marker + extracted_driver_reloc = sosdriverfile[drivers_list[i]['reloc_start']+2:drivers_end] #skip the reloc length(+2) + #print extracted_driver_reloc.encode('hex') + + #convert the reloc table from little endian addresses to list of integers + offset_table = [] + for i in range (0,len(extracted_driver_reloc),2): + offset_table.append(readWord(extracted_driver_reloc,i)) + + #now lets relocate the code to 0x2000 + #just updates the high byte of the addresses to 0x20 + j = 0 + for i in range(0,len(extracted_driver_code)): + byte = readByte(extracted_driver_code,i) + if i == offset_table[j]+1: #looking at high byte + extracted_driver_code = extracted_driver_code[:i] + chr(byte + 0x20) + extracted_driver_code[i+1:] #add to the existing address high byte + if j < (len(offset_table)-1): + j += 1 + + filename = driver_name[1:] + '.driver_code_0x2000' #chop off the ., and add .driver_code_0x2000 to the end + driverfile = open(filename,'wb') #write the new driver out + driverfile.write(extracted_driver_code) + driverfile.close() + + print 'Driver: ' + driver_name + ' extracted, relocated and written to file: ' + filename + + else: + #not found + print 'Driver: ' + driver_name + ' not found in SOS.DRIVER file' diff --git a/Apple3_o65.cfg b/Apple3_o65.cfg new file mode 100644 index 0000000..b3e4382 --- /dev/null +++ b/Apple3_o65.cfg @@ -0,0 +1,21 @@ +MEMORY +{ + ROM: start=$0, size=$b800, type=ro, define=yes, file=%O; + RAM: start=$0, size=$b800, type=rw, define=yes, file=%O; +} + +SEGMENTS +{ + TEXT: load=ROM, type=ro; + DATA: load=RAM, type=rw; +} + +FILES +{ + %O: format = o65; +} + +FORMATS +{ + o65: os = lunix, version = 0, type = small; +} \ No newline at end of file