mirror of
https://github.com/elliotnunn/ToolboxToolbox.git
synced 2024-09-27 19:55:37 +00:00
246 lines
7.1 KiB
Python
Executable File
246 lines
7.1 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
|
|
|
|
# Pipe the output of MPW DumpObj into this script (never mind line endings).
|
|
# The output will be a good template for decompiling MPW C code by hand.
|
|
|
|
|
|
# Copyright (c) 2020 Elliot Nunn
|
|
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
|
|
# The above copyright notice and this permission notice shall be included in all
|
|
# copies or substantial portions of the Software.
|
|
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
# SOFTWARE.
|
|
|
|
|
|
import sys
|
|
import re
|
|
lines = sys.stdin.read().replace('\r', '\n').split('\n')
|
|
|
|
def match(*args, **kwargs):
|
|
global m
|
|
m = re.match(*args, **kwargs)
|
|
return m
|
|
|
|
def search(*args, **kwargs):
|
|
global m
|
|
m = re.search(*args, **kwargs)
|
|
return m
|
|
|
|
|
|
def gethex(s):
|
|
try:
|
|
return int(s.replace('$', '').replace('#', '').replace('0x', ''), 16)
|
|
except:
|
|
raise ValueError('%r not hexable' % s)
|
|
|
|
|
|
name = 'NONAME'
|
|
firstline = -1
|
|
|
|
procedures = []
|
|
for i, l in enumerate(lines):
|
|
if search(r'Module="(\w+)"', l): name = m.group(1)
|
|
|
|
if search(r'LINK.W +A6', l):
|
|
firstline = i
|
|
leading_chars = m.start()
|
|
|
|
if firstline != -1 and match(r'^(\w+):', l) and not match(r'^[0-9A-Fa-f]+$', m.group(1)):
|
|
name = 'NONAME'
|
|
firstline = -1
|
|
continue
|
|
|
|
if search(r'\b(RTS|RTD|JMP +\(A0\))\b', l):
|
|
lastline = i
|
|
if name != 'NONAME' and firstline != -1:
|
|
procedures.append((name, firstline, lastline))
|
|
name = 'NONAME'
|
|
firstline = -1
|
|
|
|
for name, firstline, lastline in procedures:
|
|
data = b''
|
|
for l in lines[lastline+1:]:
|
|
if search(r'LINK.W +A6', l): break
|
|
if not search(r':((?: [0-9A-Fa-f]{4})+) ', l): break
|
|
data += bytes.fromhex(m.group(1))
|
|
|
|
|
|
proc_offset = int(lines[firstline][:8], 16)
|
|
data_offset = int(lines[lastline][:8], 16) + 2
|
|
if 'RTD' in lines[lastline]: data_offset += 2 # ugly way to figure out where data goes
|
|
|
|
|
|
for i in range(firstline, lastline+1):
|
|
lines[i] = lines[i][:leading_chars] + lines[i][leading_chars:].partition(';')[0].rstrip()
|
|
lines[i]
|
|
|
|
statements = ''
|
|
for i in range(firstline, lastline+1):
|
|
if lines[i][8:9] != ':': continue
|
|
offset = int(lines[i][:8], 16)
|
|
opcode = lines[i][leading_chars:]
|
|
opcode, _, rest = opcode.partition(' ')
|
|
if rest:
|
|
rest = rest.strip()
|
|
|
|
rest = re.sub(r'\*[\-\+]\$[0-9A-Fa-f]+', lambda m: 'label_%X' % (int(m.group()[1:].replace('$', ''), 16) + offset), rest)
|
|
|
|
statements += 'label_%X:' % offset
|
|
|
|
if rest:
|
|
statements += opcode + ' ' + rest + ';'
|
|
else:
|
|
statements += opcode + ';'
|
|
|
|
|
|
# Now we have a clean statement list that we can do transformations on.
|
|
|
|
|
|
# Transformation: A7 to SP
|
|
statements = re.sub(r'(?<!\$)\bA7\b', 'SP', statements)
|
|
|
|
|
|
|
|
# Transformation: get rid of unused labels
|
|
def label_if_nonunique(m):
|
|
if len(re.findall(r'\b' + m.group()[:-1] + r'\b', statements)) > 1:
|
|
return m.group()
|
|
else:
|
|
return ''
|
|
|
|
statements = re.sub(r'\blabel_[0-9A-Fa-f]+:', label_if_nonunique, statements)
|
|
|
|
|
|
|
|
# Transformation: split the A6 stack frame into variables
|
|
a6_size = -gethex(re.search(r'LINK.W A6,#(-?\$\w\w\w\w)', statements).group(1))
|
|
a6_splits = {0, a6_size}
|
|
|
|
def a6_sub(m):
|
|
this_split = int(m.group(1), 16)
|
|
if this_split <= a6_size:
|
|
a6_splits.add(this_split)
|
|
return 'var_%X' % this_split
|
|
else:
|
|
return m.group()
|
|
|
|
statements = re.sub(r'-\$(\w\w\w\w)\(A6\)', a6_sub, statements)
|
|
a6_splits = sorted(a6_splits)
|
|
|
|
a6_decs = ''
|
|
for a, b in zip(a6_splits, a6_splits[1:]):
|
|
varsize = b-a
|
|
if varsize == 1:
|
|
a6_decs += 'char var_%X;' % b
|
|
elif varsize == 2:
|
|
a6_decs += 'short var_%X;' % b
|
|
elif varsize == 4:
|
|
a6_decs += 'long var_%X;' % b
|
|
else:
|
|
a6_decs += 'char var_%X[0x%x];' % (b, varsize)
|
|
|
|
|
|
|
|
# Transformation: change #$70777063 to #'pwpc'
|
|
def longliteral(m):
|
|
chars = bytes.fromhex(m.group(1)).decode('ascii', 'replace')
|
|
if all(c.isalnum() or c in ' #' for c in chars):
|
|
return repr(chars)
|
|
else:
|
|
return m.group()
|
|
|
|
statements = re.sub(r'\$([0-9A-Fa-f]{8})\b', longliteral, statements)
|
|
|
|
|
|
|
|
# Transformation: change $ to 0x
|
|
statements = statements.replace('$', '0x')
|
|
|
|
|
|
|
|
# Transformation: split the trailing data into string literals
|
|
data_splits = {data_offset}
|
|
for m in re.finditer(r'\blabel_([0-9A-Fa-f]+)\b', statements):
|
|
offset = int(m.group(1), 16)
|
|
if data_offset <= offset < data_offset + len(data):
|
|
data_splits.add(offset)
|
|
data_splits = sorted(data_splits)
|
|
|
|
def data_sub(m):
|
|
this_offset = int(m.group(1), 16)
|
|
if not (data_offset <= this_offset < data_offset + len(data)): return m.group()
|
|
|
|
chars = b''
|
|
for i in range(len(data)):
|
|
if i > 0 and data_offset+i in data_splits: break
|
|
chars += data[i:i+1]
|
|
|
|
chars = chars.rstrip(b'\x00')
|
|
|
|
quotedstring = ''
|
|
if chars and chars[0] == len(chars) - 1:
|
|
quotedstring += '\\p'
|
|
chars = chars[1:]
|
|
|
|
for c in chars:
|
|
if c == 10:
|
|
quotedstring += '\\r' # deliberately reversed CR and LF
|
|
elif c == 13:
|
|
quotedstring += '\\n'
|
|
elif c == ord('"'):
|
|
quotedstring += '\\"'
|
|
elif 32 <= c < 127:
|
|
quotedstring += chr(c)
|
|
else:
|
|
quotedstring += '\\x%02X' % c
|
|
return '"%s"' % quotedstring
|
|
|
|
statements = re.sub(r'\blabel_([0-9A-Fa-f]+)\b', data_sub, statements)
|
|
|
|
|
|
|
|
# Does it use the self-argument-cleaning Pascal convention
|
|
prototype = 'void %s(void)' % name
|
|
if statements.split(';')[-2].startswith(('JMP (A0)', 'RTD ')): prototype = 'pascal ' + prototype
|
|
|
|
|
|
|
|
def splitstatements(longstring):
|
|
return re.findall(r'[^;:]+.', longstring)
|
|
|
|
|
|
print(prototype)
|
|
print('{')
|
|
|
|
for x in splitstatements(a6_decs):
|
|
print(' ' + x)
|
|
|
|
if a6_decs: print()
|
|
|
|
for x in splitstatements(statements):
|
|
if x.endswith(';'):
|
|
a, b, c = x.partition(' ')
|
|
if c:
|
|
b = ' ' * (8 - len(a))
|
|
x = a + b + c
|
|
x = '// ' + x
|
|
print(' ' + x)
|
|
|
|
print('}')
|
|
print()
|
|
print()
|