diff --git a/Driv3rs.py b/Driv3rs.py index ee01d5e..d2a7876 100644 --- a/Driv3rs.py +++ b/Driv3rs.py @@ -1,14 +1,23 @@ +# use unpack from struct and argv from sys from struct import unpack; from sys import argv +# setup args as a variable to hold argv -- there will be three +# in total script, input file, output file. args = argv -usage = "\nUsage: python Driv3rs.py [input_file] [output_file]\n" +usage = "\nUsage: python Driv3rs.py [disk_img] [output_file.csv]\n" +# check that user passed required number of arguments if len(args) < 3: print usage exit() +# stick passed arugments into variables for later disk_img = args[1] output_csv = args[2] +# 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(bytes, **options): if options.get("type") == 't': SOS = SOSfile.read(bytes) @@ -25,15 +34,18 @@ def readUnpack(bytes, **options): offset_unpacked = unpack ('< B', SOS) return int(ord(SOS)) +# this function takes a byte and performs bit operations +# to determine integer value. Outputs as a string. Used in +# the versioning DIB pullout. Values are stored in HEX. def nibblize(byte, **options): if options.get("direction") == 'high': return str(int(hex(byte >> 4), 0)) if options.get("direction") == 'low': return str(int(hex(byte & 0x0F), 0)) - +# dictionary for device types and sub types. dev_types ={273: 'Character Device, Write-Only, Formatter', - 321: 'Character Device, Write-Only, RS232 Printer', # dictionary for types and subtypes + 321: 'Character Device, Write-Only, RS232 Printer', 577: 'Character Device, Write-Only, Silentype', 833: 'Character Device, Write-Only, Parallel Printer', 323: 'Character Device, Write-Only, Sound Port', @@ -45,121 +57,217 @@ dev_types ={273: 'Character Device, Write-Only, Formatter', 721: 'Block Device, PROFile', 4337: 'Block Device, CFFA3000'} -mfgs = {17491: 'David Schmidt'} # dictionary for manufacturers +# dictionary for known manufacturers. currently only David Schmidt +# and Apple Computer are known. Apple Computer is a defined as a range +# from 1-31. +mfgs = {17491: 'David Schmidt'} -SOSfile = open(disk_img, 'rb') # open file to interrogate -filetype = readUnpack(8, type = 't') # read first eight bytes and stick returned text into filetype +# open SOS.DRIVER file to interrogate, then read first +# eight bytes and determine if file is actual SOS.DRIVER file. +# will be replaced with logic to read full disk images (PRODOS) +SOSfile = open(disk_img, 'rb') +filetype = readUnpack(8, type = 't') -if filetype == 'SOS DRVR': # is filetype 'SOS DRVR'?? - print "This is a proper SOS.DRIVER file." # output, this is a proper file -else: # otherwise.... - print "This is not a proper SOS.DRIVER file" # ouput, this is not a proper SOS file - exit() # nothing to be done, exit script +if filetype == 'SOS DRVR': + print "This is a proper SOS.DRIVER file." +else: + print "This is not a proper SOS.DRIVER file" + exit() +# read two bytes immediately after "SOS DRVR" to determine jump +# to first major driver. Print out what's found. Start a count of +# found major drivers for upcoming loop and initalize a list to +# hold driver dictionaries. +rel_offset = readUnpack(2, type = 'b') +print "The first relative offset value is", rel_offset, hex(rel_offset) +drivers = 0 +drivers_list=[] -rel_offset = readUnpack(2, type = 'b') # read two bytes after SOS DRVR to establish first rel_offset value -print "The first relative offset value is", rel_offset, hex(rel_offset) # print out for debug -drivers = 0 # this is to keep a running total of drivers. -drivers_list=[] # initialize a list to hold dictionaries. +# loop that determines beginning of all major drivers in this particular +# SOS.DRIVER file, logs the offsets into a dictionary for later use. +# for each driver found, we: +# 1. Jump past the bytes indicating the comment length. +# 2. Look at the next two bytes and determine if they are xFFFF. +# 3. If not, place offset in dictionary. +# 4. When FFFF is encountered, we've reached the end of all drivers. +loop = True +while loop : + driver = {} + SOSfile.seek(rel_offset,1) + driver['comment_start'] = SOSfile.tell() + rel_offset = readUnpack(2, type = 'b') + if rel_offset == 0xFFFF: + loop = False + else : + drivers_list.append(driver) + SOSfile.seek(rel_offset,1) + rel_offset = readUnpack(2, type = 'b') + SOSfile.seek(rel_offset,1) + rel_offset = readUnpack(2, type = 'b') -loop = True # set True for major driver loop -while loop : # establish loop to find major drivers - driver = {} # initialize a dictionary to hold each major driver - SOSfile.seek(rel_offset,1) # jump past comment - atell = SOSfile.tell() - driver['comment_start'] = SOSfile.tell() # add new driver location to driver dictionary - rel_offset = readUnpack(2, type = 'b') # grab next two bytes to set up jump and to see if we're at the end of drivers - atell = SOSfile.tell() - if rel_offset == 0xFFFF: # if we encounter FFFF, no more drivers and - loop = False # set loop to False; no more drivers - else : # otherwise.... - drivers_list.append(driver) # append a new list entry - SOSfile.seek(rel_offset,1) # jump (1 of 2) distance found in previous 2 bytes read - rel_offset = readUnpack(2, type = 'b') # read 2 bytes to set up next jump - SOSfile.seek(rel_offset,1) # jump (2 of 2) distance found in previous two bytes - rel_offset = readUnpack(2, type = 'b') # we're now at the comment_len of a new major driver; read two bytes for length +# utilizing the offsets found, we now push through each Device information +# Block to log information about a driver. Comments therein. -for i in range(0,len(drivers_list)): # begin loop to grab information from DIB - SOSfile.seek(drivers_list[i]['comment_start'],0) # reference SOF and seek to beginning of major driver - comment_len = readUnpack(2, type = 'b') # grab comment length (2 bytes) - drivers_list[i]['comment_len'] = comment_len # store comment length - if comment_len != 0x0000: # if comment length is not 0000 - comment_txt = readUnpack(comment_len, type = 't') # comment_len bytes as text - drivers_list[i]['comment_txt'] = comment_txt # place comment in dictionary - else: # otherwise... - drivers_list[i]['comment_txt'] = 'None' # else enter comment as nothing - SOSfile.seek(2,1) # skip two bytes immediately following comment or 0000 +# first deal with comment length and comment text. +# comment length is two bytes (either the length in hex or 0x0000) +# log both to list/dictionary +for i in range(0,len(drivers_list)): + SOSfile.seek(drivers_list[i]['comment_start'],0) + comment_len = readUnpack(2, type = 'b') + drivers_list[i]['comment_len'] = comment_len + if comment_len != 0x0000: + comment_txt = readUnpack(comment_len, type = 't') + drivers_list[i]['comment_txt'] = comment_txt + else: + drivers_list[i]['comment_txt'] = 'None' + + # these two bytes are the intermediate offset value used to jump + # to the next major driver. We skip here while going through the DIB. + SOSfile.seek(2,1) + + # Officially, the link pointer is the beginning of the DIB. + # comments are optional. We log dib_start to indicate where the + # actual DIB for a driver begins. drivers_list[i]['dib_start'] = SOSfile.tell() - link_ptr = readUnpack(2, type = 'b') # grab link pointer - drivers_list[i]['link_ptr'] = link_ptr # store link pointer - entry = readUnpack(2, type = 'b') # grab entry field - drivers_list[i]['entry'] = entry # store entry field - name_len = readUnpack(1, type = '1') # grab one byte for name length - drivers_list[i]['name_len'] = name_len # store name length - name = readUnpack(name_len, type = 't') # read name based on length - drivers_list[i]['name'] = name # store name - SOSfile.seek(15 - name_len,1) # skip past remainder of 15-byte long name field - flag = readUnpack(1, type = '1') # grab Flag byte - if flag == 192: # is flag 0xc0? - drivers_list[i]['flag'] = 'ACTIVE, Load on Boundary' # yes, ACTIVE and load on boundary - elif flag == 128: # is flag 0x80? - drivers_list[i]['flag'] = 'ACTIVE' # insert Flag into dictionary with value ACTIVE - else: # otherwise... - drivers_list[i]['flag'] = 'INACTIVE' # insert Flag into dictionary with value INACTIVE - slot_num = readUnpack(1, type = '1') # grab slot number - if slot_num == 0: # check if device has no slot number... - drivers_list[i]['slot_num'] = 'None' # ... and indicate so. - else: # otherwise... - drivers_list[i]['slot_num'] = slot_num # ... enter slot number into the dictionary - unit = readUnpack(1, type = '1') # get the unit byte - drivers_list[i]['unit'] = unit # store unit in dictionary + + # the link pointer is two bytes and points to the next DIB + # when there are multiples -- .D1, .D2, etc. + link_ptr = readUnpack(2, type = 'b') + drivers_list[i]['link_ptr'] = link_ptr + + # entry field is two bytes pointing to the area in memory + # where the driver is placed during SOS bootup. + entry = readUnpack(2, type = 'b') + drivers_list[i]['entry'] = entry + + # the name length and name are next. Name length is one byte. + # name field is _always_ 15 bytes long but name can be anything + # up to 15 bytes + name_len = readUnpack(1, type = '1') + drivers_list[i]['name_len'] = name_len + name = readUnpack(name_len, type = 't') + drivers_list[i]['name'] = name + SOSfile.seek(15 - name_len,1) + + # flag byte determine whether a driver is active, inactive + # or shall be loaded on a page boundary. This is set up in + # the System Configuration Program + flag = readUnpack(1, type = '1') + if flag == 192: + drivers_list[i]['flag'] = 'ACTIVE, Load on Boundary' + elif flag == 128: + drivers_list[i]['flag'] = 'ACTIVE' + else: + drivers_list[i]['flag'] = 'INACTIVE' + + # if the driver is for a card that is loaded into a slot + # and that slot has been defined in the SCP and placed into + # the current SOS.DRIVER file, we log it here. + slot_num = readUnpack(1, type = '1') + if slot_num == 0: + drivers_list[i]['slot_num'] = 'None' + else: + drivers_list[i]['slot_num'] = slot_num + + # the unit byte is concerned with the device number encountered. + # for the major drivers, it's always 0 and if there are other + # DIBs, such as for a driver that supports multiple disk drives, + # the unit byte is incremented each time by 1. + unit = readUnpack(1, type = '1') + drivers_list[i]['unit'] = unit + + # dev_type is the type of device. We currently use a dict + # and populate the field accordingly. This dictionary was + # built from Apple's published Driver Writer's Manual. + # The type is determined via two bytes. The LSB is the sub-type + # and the MSB is the type. dev_type = readUnpack(2, type ='b') try: - drivers_list[i]['dev_type'] = dev_types[dev_type] # insert device type into dictionary + drivers_list[i]['dev_type'] = dev_types[dev_type] except: drivers_list[i]['dev_type'] = 'Unknown' + + # we skip the Filler byte ($19) as Apple reserved it. SOSfile.seek(1,1) - block_num = readUnpack(2, type = 'b') # get block_num - if block_num != 0: # is block_num not zero? - drivers_list[i]['block_num'] = block_num # yes, store retrieved block_num value - else: # otherwise... - drivers_list[i]['block_num'] = 'Character Device or Undefined' # log field as char device or undefined - mfg = readUnpack(2, type = 'b') # read mfg bytes + + # block_num refers to the number of logical blocks in a device + # it is not guaranteed to be populated with anything + # we log the block number defined or that the the device is + # a character device. otherwise Undefined + block_num = readUnpack(2, type = 'b') + if block_num != 0: + drivers_list[i]['block_num'] = block_num + else: + drivers_list[i]['block_num'] = 'Character Device or Undefined' + + # the manufacturer byte was ill-defined at the time the driver + # writer's manual was published. 1 through 31 were reserved for + # Apple Computer. Others were supposed to get their codes from + # Apple. At the time we wrote this script, we used Apple devices + # and the CFFA3000 and populated a dictionary. This dictionary + # will get more k/v pairs as time goes on. + mfg = readUnpack(2, type = 'b') try: - drivers_list[i]['mfg'] = mfgs[mfg] # try to match key and place in dictionary + drivers_list[i]['mfg'] = mfgs[mfg] except: - if 1 <= mfg <= 31: # if exception, check if mfg is between 1 and 31 - drivers_list[i]['mfg'] = 'Apple Computer' # yes, this is Apple Computer + if 1 <= mfg <= 31: + drivers_list[i]['mfg'] = 'Apple Computer' else: - drivers_list[i]['mfg'] = 'Unknown' # no, this is unknown. log as such - ver_byte0 = readUnpack(1, type = '1') # start version field -- grab the V/v0 numbers | V = high-nibble, v0 = low-nibble - ver_byte1 = readUnpack(1, type = '1') # grab the v1/Q numbers | v1 = high-nibble, Q = low-nibble - V = nibblize(ver_byte1, direction = 'high') # grab major version number as string - v0 = nibblize(ver_byte1, direction = 'low') # grab first minor version number (v0) as string - v1 = nibblize(ver_byte0, direction = 'high') # grab second minor version number (v1) as string - Q = nibblize(ver_byte0, direction = 'low') # grab qualifier number. We only care about A (Alpha), B (Beta), or E (Experimental) - drivers_list[i]['version'] = V + '.' + v0 + v1 + Q # put version number into dictionary + drivers_list[i]['mfg'] = 'Unknown' -for i in range(0,len(drivers_list)): # begin search for how many devices a driver can support - link_ptr = drivers_list[i]['link_ptr'] # grab link_ptr in dictionary - total_devs = 1 # initialize devices count (initially 1) - if link_ptr != 0x0000: # is link_ptr not 0x0000? - SOSfile.seek(drivers_list[i]['dib_start'],0) # seek to beginning of major driver from start of file - SOSfile.seek(drivers_list[i]['link_ptr'],1) # seek to beginning of sub-DIB - sub_loop = True # setup loop to run through sub-drivers - while sub_loop: # begin loop to move only through sub- - sub_link = readUnpack(2, type = 'b') # read link field of sub-driver - if sub_link != 0x0000: # if link field is not zero... - total_devs = total_devs + 1 # increment sub_drivers count - SOSfile.seek(32,1) # seek to next sub-DIB ($22 bytes minus link field) - else: # else... - sub_loop = False # set inner loop to false # set sub-driver loop to false - drivers_list[i]['num_devices'] = total_devs # place total number in dictionary + # version bytes are integer values stored across two bytes. + # a nibble corresponds to a major version number, one of two minor + # version numbers, or a "further qualification" as Apple + # called it. The format is V-v0-v1-Q. Q was not well-defined. + # Basically if the value is 0xA, 0xB, or 0xE, then the Q + # corresponds to: Alpha, Beta, and Experimental. Otherwise, + # Q is merely a number. Q was worked out via the SCP. + ver_byte0 = readUnpack(1, type = '1') + ver_byte1 = readUnpack(1, type = '1') + V = nibblize(ver_byte1, direction = 'high') + v0 = nibblize(ver_byte1, direction = 'low') + v1 = nibblize(ver_byte0, direction = 'high') + Q = nibblize(ver_byte0, direction = 'low') + if Q == '10': + drivers_list[i]['version'] = V + '.' + v0 + v1 + ' Alpha' + elif Q == '11': + drivers_list[i]['version'] = V + '.' + v0 + v1 + ' Beta' + elif Q == '14': + drivers_list[i]['version'] = V + '.' + v0 + v1 + ' Experimental' + else: + drivers_list[i]['version'] = V + '.' + v0 + v1 -print drivers_list[0]['dev_type'] +# here we run a new loop to determine how many other DIBs exist +# under a major driver. This is primarily for drivers that are designed +# to support more than one device. for instance, the CFFA3000 is +# written to support seven drives (.D1 - .D7). otherwise, nothing +# else changes save the unit number, which is incremented by 1 for +# each device supported. +# generally we enter each major drive DIB and look at the link field. +# if the link field is not 0000, we know there are other DIBs. +# we can open up a new loop to run through the interior DIBs until +# we encount a 0000 in a link field, then we stop. +for i in range(0,len(drivers_list)): + link_ptr = drivers_list[i]['link_ptr'] + total_devs = 1 + if link_ptr != 0x0000: + SOSfile.seek(drivers_list[i]['dib_start'],0) + SOSfile.seek(drivers_list[i]['link_ptr'],1) + sub_loop = True + while sub_loop: + sub_link = readUnpack(2, type = 'b') + if sub_link != 0x0000: + total_devs = total_devs + 1 + SOSfile.seek(32,1) + else: + sub_loop = False + drivers_list[i]['num_devices'] = total_devs -SOSfile.close() # no need for it anymore +# closing the SOS.DRIVER file +SOSfile.close() +# here begins writing out the CSV file. the order is mainly +# structured like the structure in the Driver Writer's Manual. csvout = open(output_csv, 'w') csvout.write('comment_start,comment_len,comment_txt,dib_start,\ link_ptr,entry,name_len,name,flag,slot_num,num_devices,unit,dev_type,\ diff --git a/output.csv b/output.csv index e25162f..38f599f 100644 --- a/output.csv +++ b/output.csv @@ -1,6 +1,6 @@ -comment_start,comment_len,comment_txt,dib_start,link_ptr,entry,name_len,name,flag,slot_num,num_devices,unit,dev_type,block_num,mfg,version -0x52c,0x0,None,0x530,0x43e,0x3bf,0x6,.FMTD1,"ACTIVE, Load on Boundary",None,3,0,"Character Device, Write-Only, Formatter",280,Apple Computer,1.1 -0xa94,0x0,None,0xa98,0x0,0x10b,0xa,.SILENTYPE,INACTIVE,None,1,0,"Character Device, Write-Only, Silentype",Character Device or Undefined,Apple Computer,1.04 -0x1d72,0x0,None,0x1d76,0x0,0xc0,0x8,.PRINTER,ACTIVE,None,1,0,"Character Device, Write-Only, RS232 Printer",Character Device or Undefined,Apple Computer,1.1 -0x20fc,0x0,None,0x2100,0x0,0x10a,0x8,.CONSOLE,ACTIVE,None,1,0,"Character Device, Read-Write, System Console",Character Device or Undefined,Apple Computer,1.12 -0x34ee,0x4e,Apple /// CFFA3000 (Compact Flash For Apple 3000) Driver by David Schmidt 2011,0x3540,0x22,0x132,0xb,.CFFA3000D1,ACTIVE,1,7,0,"Block Device, CFFA3000",Character Device or Undefined,David Schmidt,1 \ No newline at end of file +comment_start,comment_len,comment_txt,dib_start,link_ptr,entry,name_len,name,flag,slot_num,num_devices,unit,dev_type,block_num,mfg,version +0x52c,0x0,None,0x530,0x43e,0x3bf,0x6,.FMTD1,"ACTIVE, Load on Boundary",None,3,0,"Character Device, Write-Only, Formatter",280,Apple Computer,1.10 +0xa94,0x0,None,0xa98,0x0,0x10b,0xa,.SILENTYPE,"INACTIVE",None,1,0,"Character Device, Write-Only, Silentype",Character Device or Undefined,Apple Computer,1.04 +0x1d72,0x0,None,0x1d76,0x0,0xc0,0x8,.PRINTER,"ACTIVE",None,1,0,"Character Device, Write-Only, RS232 Printer",Character Device or Undefined,Apple Computer,1.10 +0x20fc,0x0,None,0x2100,0x0,0x10a,0x8,.CONSOLE,"ACTIVE",None,1,0,"Character Device, Read-Write, System Console",Character Device or Undefined,Apple Computer,1.12 +0x34ee,0x4e,Apple /// CFFA3000 (Compact Flash For Apple 3000) Driver by David Schmidt 2011,0x3540,0x22,0x132,0xb,.CFFA3000D1,"ACTIVE",1,7,0,"Block Device, CFFA3000",Character Device or Undefined,David Schmidt,1.00