PowerRomDasm/extractdict.py

119 lines
3.5 KiB
Python

'''
Dictionary extraction script for Apple OpenFirmware.
Author: Max Poliakovski 2019-2021
'''
import os
import struct
from argparse import ArgumentParser
class OFWordHeader:
def __init__(self, infile, pos):
infile.seek(pos)
# get common fields
hdr = struct.unpack('>iBBH', infile.read(8))
self.prev = hdr[0]
self.flags = hdr[1]
self.type = hdr[2]
self.tok_num = hdr[3]
if self.flags & 0x20: # bit 5 means nameless word
# generate artificial name
self.name = 'unnamed_' + format(self.tok_num, 'x')
else:
len = struct.unpack('B', infile.read(1))[0]
self.name = struct.unpack('%ds' % len,
infile.read(len))[0].decode('utf-8')
self.pos = pos
def parse_coff_container(infile, cont_offset):
infile.seek(cont_offset)
# read COFF header
coff_hdr = struct.unpack('>HHL', infile.read(8))
n_sections = coff_hdr[1]
# COFF magic and at least one section are required
if coff_hdr[0] != 0x1DF or n_sections < 1:
print("No valid COFF header found at offset %X" % cont_offset)
return (0, 0)
if coff_hdr[2] == 0x47617279:
print("Detected Macintosh OldWorld OF binary...")
infile.seek(cont_offset + 20) # rewind to sections array
# search for executable code section
for sect in range(n_sections):
sect_desc = struct.unpack('>8sLLLLLLHHL', infile.read(40))
sect_name = sect_desc[0].decode('utf-8').strip('\x00')
if sect_name == '.text':
return (sect_desc[4], sect_desc[3])
return (0, 0)
def scan_forth_dict(infile, pos, end_pos):
# try offset at code_section[0x48] that usually points
# to the header of the last word (cold-load)
infile.seek(pos + 0x48)
dict_last_offset = struct.unpack('>L', infile.read(4))[0]
if (dict_last_offset + 20) >= end_pos:
return 0
word = OFWordHeader(infile, dict_last_offset + pos)
if word.name == 'cold-load':
print("cold-load found at offset %X" % word.pos)
else:
print('Scanning for cold-load not implemented yet')
return 0
print('\n')
forth_dict = {}
word_pos = dict_last_offset + pos
while 1:
forth_dict[word.tok_num] = {'name' : word.name, 'type' : word.type, 'pos' : word.pos}
if word.prev >= 0:
return forth_dict
word_pos += word.prev
del word
word = OFWordHeader(infile, word_pos)
def print_dict(dict):
for tok_num, word in dict.items():
print("Word: %04X, name: %s, type: %02X, offset = %08X" % (tok_num, word['name'], word['type'], word['pos']))
def main():
parser = ArgumentParser()
parser.add_argument('--rom_path', type=str,
dest='rom_path',
help='path to ROM file to process',
metavar='ROM_PATH', required=True)
parser.add_argument('--offset', type=lambda x: int(x,0),
dest='of_offset',
help='offset to OF container (autodetect attempt if omitted)',
metavar='OF_OFFSET', required=True)
opts = parser.parse_args()
with open(opts.rom_path, 'rb') as infile:
pos, size = parse_coff_container(infile, opts.of_offset);
if size == 0:
print("No valid OF binary found at offset %X" % opts.of_offset)
exit(1)
dict = scan_forth_dict(infile, opts.of_offset + pos, pos + size)
print_dict(dict)
if __name__ == '__main__':
main()