-
Notifications
You must be signed in to change notification settings - Fork 2
/
Driv3rs.py
468 lines (411 loc) · 18.2 KB
/
Driv3rs.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
# use unpack from struct and argv from sys
from struct import unpack; import argparse
import hashlib
import os.path
parser = argparse.ArgumentParser(
prog='Driv3rs.py',
formatter_class=argparse.RawDescriptionHelpFormatter,
description='''\
****************************************************************
* Driv3rs.py - A tiny Python script to help catalog drivers on *
* imaged Apple /// disks. By Mike Whalen, Michael Sternberg *
* and Paul Hagstrom. Please submit pull requests to Github. *
* https://github.com/thecompu/Driv3rs *
* Special Thanks to Rob Justice for Bug Fixes and Suggestions *
****************************************************************
''')
group = parser.add_mutually_exclusive_group()
group.add_argument("-rh", "--rawhex", action="store_true", help="Store hex values in CSV")
group.add_argument("-rd", "--rawdec", action="store_true", help="Store decimal values in CSV")
parser.add_argument("sosfile", help="sos.driver file to parse")
parser.add_argument("csvfile", help="csv file to create")
args = parser.parse_args()
# stick passed arugments into variables for later
disk_img = args.sosfile
output_csv = args.csvfile
# 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)
text_unpacked = unpack('%ss' % bytes, SOS)
return ''.join(text_unpacked)
if options.get("type") == 'b':
SOS = SOSfile.read(bytes)
offset_unpacked = unpack ('< H', SOS)
return int('.'.join(str(x) for x in offset_unpacked))
if options.get("type") == '1':
SOS = SOSfile.read(bytes)
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))
def device_type_string(byte):
retstr = ""
if byte & 128 == 128:
retstr = "Block Device"
if byte & 64 == 64:
retstr = retstr + ", Read-Write"
else:
retstr = retstr + ", Read-Only"
if byte & 32 == 32:
retstr = retstr + ", Removable"
else:
retstr = retstr + ", Non-Removable"
if byte & 16 == 16:
retstr = retstr + ", Formatter Present"
else:
retstr = retstr + ", No Formatter"
else:
retstr = "Character Device"
if byte & 64 == 64:
retstr = retstr + ", Write Allowed"
else:
retstr = retstr + ", Write Disabled"
if byte & 32 == 32:
retstr = retstr + ", Read Allowed"
else:
retstr = retstr + ", Read Disabled"
return retstr
# dictionary for device types and sub types.
dev_types ={273: 'Character Device, Write-Only, Formatter', # $11/$01
321: 'Character Device, Write-Only, RS232 Printer', # $41/$01
577: 'Character Device, Write-Only, Silentype', # $41/$02
833: 'Character Device, Write-Only, Parallel Printer', # $41/$03
323: 'Character Device, Write-Only, Sound Port', # $43/$01
353: 'Character Device, Read-Write, System Console', # $61/$01
354: 'Character Device, Read-Write, Graphics Screen', # $62/$01
355: 'Character Device, Read-Write, Onboard RS232', # $63/$01
356: 'Character Device, Read-Write, Parallel Card', # $64/$01
273: 'Disk /// Formatter', # $11/$01
481: 'Block Device, Disk ///', # $E1/$01
721: 'Block Device, ProFile', # $D1/$02
4337: 'Block Device, CFFA3000'} # $F1/$10
# Dictionary for known manufacturers.
# Apple Computer is a defined as a range from 1-31.
mfgs = {35: 'Quark Incorporated',
13107: 'Bob Consorti: ON THREE Inc.',
17491: 'David Schmidt',
21066: 'Rob Justice'}
# 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':
print "Valid SOS.DRIVER file: {}".format(disk_img)
else:
print "INVALID SOS.DRIVER file: {}".format(disk_img)
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=[]
# 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')
# utilizing the offsets found, we now push through each Device information
# Block to log information about a driver. Comments therein.
# For nearly all entries, we write decimal values to our initial dictionaries
# then convert to desired output at csv time. Excluded would be ascii strings
# and the version numbers.
# 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.replace('"', "''") # quotation marks will mess up csv
else:
drivers_list[i]['comment_txt'] = 'None'
# these two bytes are the intermediate offset value used to jump
# to the next major driver. This is useful for computing the md5 hash.
drivers_list[i]['next_driver'] = readUnpack(2, type = 'b')
# 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()
# 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')
drivers_list[i]['flag'] = flag
# 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')
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.
drivers_list[i]['dev_type'] = readUnpack(1, type ='1')
drivers_list[i]['dev_subtype'] = readUnpack(1, type ='1')
# we skip the Filler byte ($19) as Apple reserved it.
SOSfile.seek(1,1)
# 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')
drivers_list[i]['block_num'] = block_num
# 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')
drivers_list[i]['mfg']=mfg
# 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
# device configuration block length
dcb_length = readUnpack(2, type = 'b')
drivers_list[i]['dcb_length'] = dcb_length
# calculate an md5 hash of the entire driver and of just the code
# portion
SOSfile.seek(drivers_list[i]['comment_start'], 0)
# configuration bytes include the comment and parameters that can be changed in SCP
config_bytes = SOSfile.read(4 + drivers_list[i]['comment_len'] + drivers_list[i]['entry'])
# code bytes contain the region between the entry point and the next driver
code_bytes = SOSfile.read(drivers_list[i]['next_driver'] - drivers_list[i]['entry'])
# Hash just the code portion
code_md5 = hashlib.md5(code_bytes)
# Hash the whole driver, which will include both code and parameters
driver_md5 = hashlib.md5(config_bytes)
driver_md5.update(code_bytes)
# Store the resulting hash digest hex strings
drivers_list[i]['driver_md5'] = driver_md5.hexdigest()
drivers_list[i]['code_md5'] = code_md5.hexdigest()
# 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 eight drives (.D1 - .D8). 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 encounter a 0000 in a link field, then we stop.
for i in range(0,len(drivers_list)):
total_devs = 0
SOSfile.seek(drivers_list[i]['dib_start'],0)
sub_sub = ""
drivers_list[i]['subdrivers'] = sub_sub #initialize subdrivers dict k/v
sub_loop = True
while sub_loop:
total_devs = total_devs + 1
sub_link = readUnpack(2, type = 'b') #link to next DIB
if sub_link != 0x0000:
SOSfile.seek(drivers_list[i]['dib_start'] + sub_link,0) #link is from DIB start
SOSfile.seek(4,1) #skip over dib Entry pointer
subname_len = readUnpack(1, type = '1')
subname = readUnpack(subname_len, type = 't')
sub_temp = drivers_list[i]['subdrivers']
if sub_temp != "" and sub_link != 0x0000:
sub_temp = sub_temp + ' ' + '|' + ' ' + subname
drivers_list[i]['subdrivers'] = sub_temp
else:
drivers_list[i]['subdrivers'] = subname
SOSfile.seek(drivers_list[i]['dib_start'] + sub_link,0)
else:
sub_loop = False
drivers_list[i]['num_devices'] = total_devs
# 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.
# first, check if file exists and, if so, omit header
exists = os.path.exists(output_csv)
if exists == False:
csvout = open(output_csv, 'w')
csvout.write('SOS_DRIVER_FILE,comment_start,comment_len,comment_txt,' + \
'dib_start,link_ptr,entry,name_len,majorname,flag,slot_num,num_devices,subnames,unit,' +\
'dev_type_sub,block_num,mfg,version,dcb_length,driver_md5,code_md5\n')
else:
csvout = open(output_csv, 'a')
for i in range(0,len(drivers_list)):
csvout.write(disk_img + ',')
#comment start hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['comment_start']) + ',')
else:
csvout.write(str(drivers_list[i]['comment_start']) + ',')
#comment length hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['comment_len']) + ',')
else:
csvout.write(str(drivers_list[i]['comment_len']) + ',')
#comment
csvout.write('"' + drivers_list[i]['comment_txt'] + '"' + ',')
#dib_start hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['dib_start']) + ',')
else:
csvout.write(str(drivers_list[i]['dib_start']) + ',')
#link_ptr hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['link_ptr']) + ',')
else:
csvout.write(str(drivers_list[i]['link_ptr']) + ',')
#entry hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['entry']) + ',')
else:
csvout.write(str(drivers_list[i]['entry']) + ',')
#name_len hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['name_len']) + ',')
else:
csvout.write(str(drivers_list[i]['name_len']) + ',')
#name ascii only
csvout.write(drivers_list[i]['name'] + ',' )
#flag hex or decimal or ascii
if args.rawhex:
csvout.write(hex(drivers_list[i]['flag']) + ',')
elif args.rawdec:
csvout.write(str(drivers_list[i]['flag']) + ',')
else:
if drivers_list[i]['flag'] == 192:
csvout.write('"' + 'ACTIVE, Load on Boundary' + '"' ',')
elif drivers_list[i]['flag'] == 128:
csvout.write('ACTIVE' + ',')
else:
csvout.write('INACTIVE' + ',')
#slot_num hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['slot_num']) + ',')
else:
csvout.write(str(drivers_list[i]['slot_num']) + ',')
#num_devices hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['num_devices']) + ',')
else:
csvout.write(str(drivers_list[i]['num_devices']) + ',')
#sub_driver names ascii only
csvout.write(drivers_list[i]['subdrivers'] + ',' )
#unit hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['unit']) + ',')
else:
csvout.write(str(drivers_list[i]['unit']) + ',')
#dev_type hex or decimal or ascii
if args.rawhex:
csvout.write(hex(drivers_list[i]['dev_type']) + ',')
elif args.rawdec:
csvout.write(str(drivers_list[i]['dev_type']) + ',')
else:
try:
csvout.write('"' + dev_types[drivers_list[i]['dev_subtype']*256 + drivers_list[i]['dev_type']] + '"' + ',')
except:
csvout.write('"' + device_type_string(drivers_list[i]['dev_type']) + '; Subtype '+ hex(drivers_list[i]['dev_subtype']) + '"' + ',')
#block_num hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['block_num']) + ',')
elif args.rawdec:
csvout.write(str(drivers_list[i]['block_num']) + ',')
else:
csvout.write(str(drivers_list[i]['block_num']) + ',')
#mfg hex or decimal or ascii
# csvout.write(drivers_list[i]['mfg'] + ',')
if args.rawhex:
csvout.write(hex(drivers_list[i]['mfg']) + ',')
elif args.rawdec:
csvout.write(str(drivers_list[i]['mfg']) + ',')
else:
try:
csvout.write(mfgs[(drivers_list[i]['mfg'])] + ',')
except:
if 1 <= drivers_list[i]['mfg'] <= 31:
csvout.write('Apple Computer' + ',')
else:
csvout.write(hex(drivers_list[i]['mfg']) + ' (Unknown)' + ',')
#version -- leaving as standard output
csvout.write(str(drivers_list[i]['version']) + ',')
#dcb_length hex or decimal
if args.rawhex:
csvout.write(hex(drivers_list[i]['dcb_length']) + ',')
else:
csvout.write(str(drivers_list[i]['dcb_length']) + ',')
#md5 outputs left alone
csvout.write(
drivers_list[i]['driver_md5'] + ',' + \
drivers_list[i]['code_md5']
)
csvout.write('\n')
csvout.close()