MWCC/opt_decomp.py

395 lines
15 KiB
Python

import json
import sys
def decode_avail(f):
flags = []
if (f & 0x1) != 0: flags.append('Global')
if (f & 0x2) != 0: flags.append('Sticky')
if (f & 0x4) != 0: flags.append('Cased')
if (f & 0x8) != 0: flags.append('Obsolete')
if (f & 0x10) != 0: flags.append('Substituted')
if (f & 0x20) != 0: flags.append('Deprecated')
if (f & 0x40) != 0: flags.append('Link')
if (f & 0x80) != 0: flags.append('Disasm')
if (f & 0x100) != 0: flags.append('Comp')
if (f & 0x200) != 0: flags.append('OF200')
if (f & 0x400) != 0: flags.append('OF400')
if (f & 0x800) != 0: flags.append('Ignored')
if (f & 0x1000) != 0: flags.append('Secret')
if (f & 0x2000) != 0: flags.append('HideDefault')
if (f & 0x4000) != 0: flags.append('Compat')
if (f & 0x8000) != 0: flags.append('Sub')
if (f & 0x10000) != 0: flags.append('SubsOptional')
if (f & 0x20000) != 0: flags.append('OnlyOnce')
if (f & 0x40000) != 0: flags.append('Conflicts')
if (f & 0x80000) != 0: flags.append('Warning')
if (f & 0x100000) != 0: flags.append('CanBeNegated')
if (f & 0x200000) != 0: flags.append('O_SLFlags10')
if (f & 0x400000) != 0: flags.append('O_SLFlags20')
if (f & 0x800000) != 0: flags.append('Meaningless')
if (f & 0x1000000) != 0: flags.append('OF1000000')
if (f & 0x2000000) != 0: flags.append('OF2000000')
if (f & 0x4000000) != 0: flags.append('OF4000000')
if (f & 0x8000000) != 0: flags.append('OF8000000')
if (f & 0x10000000) != 0: flags.append('OF10000000')
if (f & 0x20000000) != 0: flags.append('OF20000000')
return '|'.join(flags)
def decode_paramflags(f):
flags = []
if (f & 0x1) != 0: flags.append('PF01')
if (f & 0x2) != 0: flags.append('PF02')
if (f & 0x4) != 0: flags.append('PF04')
if (f & 0x8) != 0: flags.append('PF08')
if (f & 0x10) != 0: flags.append('PF10')
if (f & 0x20) != 0: flags.append('PF20')
if (f & 0x40) != 0: flags.append('PF40')
if (f & 0x80) != 0: flags.append('PF80')
return '|'.join(flags)
def decode_listflags(f):
flags = []
if (f & 0x1) != 0: flags.append('L_Exclusive')
if (f & 0x2) != 0: flags.append('L_AllowUnknowns')
if (f & 0x4) != 0: flags.append('LF4')
if (f & 0x8) != 0: flags.append('LF8')
if (f & 0x10) != 0: flags.append('LF10')
if (f & 0x20) != 0: flags.append('LF20')
if (f & 0x40) != 0: flags.append('LF40')
if (f & 0x80) != 0: flags.append('LF80')
if (f & 0x100) != 0: flags.append('L_Comp')
if (f & 0x200) != 0: flags.append('L_Link')
if (f & 0x400) != 0: flags.append('L_Disasm')
if (f & 0x800) != 0: flags.append('LF800')
if (f & 0x1000) != 0: flags.append('LF1000')
if (f & 0x2000) != 0: flags.append('LF2000')
if (f & 0x4000) != 0: flags.append('LF4000')
if (f & 0x8000) != 0: flags.append('LF8000')
if (f & 0x10000) != 0: flags.append('LF10000')
if (f & 0x20000) != 0: flags.append('LF20000')
if (f & 0x40000) != 0: flags.append('LF40000')
if (f & 0x80000) != 0: flags.append('LF80000')
if (f & 0x100000) != 0: flags.append('LF100000')
if (f & 0x200000) != 0: flags.append('LF200000')
if (f & 0x400000) != 0: flags.append('LF400000')
if (f & 0x800000) != 0: flags.append('LF800000')
if (f & 0x1000000) != 0: flags.append('LF1000000')
if (f & 0x2000000) != 0: flags.append('LF2000000')
if (f & 0x4000000) != 0: flags.append('LF4000000')
if (f & 0x8000000) != 0: flags.append('LF8000000')
if (f & 0x10000000) != 0: flags.append('LF10000000')
if (f & 0x20000000) != 0: flags.append('LF20000000')
if (f & 0x40000000) != 0: flags.append('LF40000000')
if (f & 0x80000000) != 0: flags.append('LF80000000')
return '|'.join(flags)
navstack = []
indent = ''
def push_indent():
global indent
indent += ' '
def pop_indent():
global indent
indent = indent[:-2]
scan_objs = {}
def register_scan_obj(name, what):
ckname = name.replace('_list', '')
if ckname[-1].isdigit():
last_non_digit = 0
for i,c in enumerate(ckname):
if not c.isdigit():
last_non_digit = i
idx = int(ckname[last_non_digit+1:])
else:
return
stk = ' / '.join(navstack)
if idx in scan_objs:
lst = scan_objs[idx]
for o in lst:
if o[1] == name:
return
lst.append((stk, name, what))
else:
scan_objs[idx] = [(stk, name, what)]
def dump_param(p):
suff = ''
if 'myname' in p:
suff += ' +myname'
if 'help' in p:
suff += ' +help'
register_scan_obj(p['_name'], f"PF:{p['flags']:02X} T:{p['_type']}{suff}")
print(f"{indent}-{p['_name']:37} | {decode_paramflags(p['flags'])} ", end='')
if 'myname' in p:
print(f"<{p['myname']}> ", end='')
if p['_type'] == 'FTypeCreator':
print(f"FTypeCreator(fc={p['fc']} iscreator={p['iscreator']})")
elif p['_type'] == 'FilePath':
print(f"FilePath(flg={p['fflags']:02X} def={p['defaultstr']} fn={p['filename']} max={p['maxlen']})")
elif p['_type'] == 'Number':
print(f"Number(sz={p['size']} fit={p['fit']} lo={p['lo']:08X} hi={p['hi']:08X} num={p['num']})")
elif p['_type'] in ('String','Id','Sym'):
print(f"{p['_type']}(max={p['maxlen']} ps={p['pstring']} s={p['str']})")
elif p['_type'] == 'OnOff':
print(f"OnOff(var={p['var']})")
elif p['_type'] == 'OffOn':
print(f"OffOn(var={p['var']})")
elif p['_type'] == 'Mask':
print(f"Mask(sz={p['size']} or={p['ormask']:08X} and={p['andmask']:08X} num={p['num']})")
elif p['_type'] == 'Toggle':
print(f"Toggle(sz={p['size']} mask={p['mask']:08X} num={p['num']})")
elif p['_type'] == 'Set':
print(f"Set(sz={p['size']} v={p['value']} num={p['num']})")
elif p['_type'] == 'SetString':
print(f"SetString(v={p['value']} ps={p['pstring']} var={p['var']})")
elif p['_type'] == 'Generic':
v = p['var']
if isinstance(v, str):
v = v.replace('\n','').replace('\r','')
print(f"Generic(p={p['parse']} var={v})")
elif p['_type'] == 'IfArg':
print('IfArg')
push_indent()
print(f'{indent}TRUE:')
push_indent()
for i,s in enumerate(p['parg']):
navstack.append(f'ifArgParam{i}')
dump_param(s)
navstack.pop(-1)
pop_indent()
print(f'{indent}FALSE:')
push_indent()
for i,s in enumerate(p['pnone']):
navstack.append(f'ifArgNoneParam{i}')
dump_param(s)
navstack.pop(-1)
pop_indent()
pop_indent()
elif p['_type'] == 'Setting':
print(f"Setting(p={p['parse']} vn={p['valuename']})")
else:
print()
def dump_opt(o):
navstack.append(o['names'])
trunchelp = o['help']
if trunchelp:
if len(trunchelp) > 35:
trunchelp = trunchelp[:20] + '...' + trunchelp[-5:]
trunchelp = trunchelp.replace('\r', ' ')
trunchelp = trunchelp.replace('\n', ' ')
avail = decode_avail(o['avail'])
register_scan_obj(o['_name'], 'OPTION ' + avail)
print(f"{indent}{o['_name']:40} | {avail} {o['names']:40} {trunchelp}")
if 'conflicts' in o:
conflicts = o['conflicts']
print(f"{indent}CONFLICTS: {conflicts['_name']} / {conflicts['_list_name']} / {decode_listflags(conflicts['flags'])}")
print(f"{indent} {conflicts['list']}")
register_scan_obj(conflicts['_name'], f"CONFLICTS LIST")
register_scan_obj(conflicts['_list_name'], f"CONFLICTS LIST NAME")
push_indent()
for i,p in enumerate(o['param']):
navstack.append(f'param{i}')
dump_param(p)
navstack.pop(-1)
pop_indent()
if 'sub' in o:
sub = o['sub']
print(f"{indent}SUB {{ {sub['_name']} / {sub['_list_name']} / {decode_listflags(sub['flags'])}")
register_scan_obj(sub['_name'], f"SUB LIST")
register_scan_obj(sub['_list_name'], f"SUB LIST NAME")
push_indent()
for s in sub['list']:
dump_opt(s)
pop_indent()
print(f"{indent}}}")
navstack.pop(-1)
def dump_optlst(ol):
register_scan_obj(ol['_name'], 'ROOT OBJECT')
register_scan_obj(ol['_list_name'], 'ROOT OBJECT LIST')
print(f"{indent}# FLAGS: {decode_listflags(ol['flags'])} - HELP: {repr(ol['help'])}")
for option in ol['list']:
dump_opt(option)
with open(sys.argv[1], 'r') as f:
optlsts = json.load(f)
for optlst in optlsts:
print(f"{indent}# --- {optlst['_name']} ---")
push_indent()
dump_optlst(optlst)
pop_indent()
print()
lastseen = 0
deltas = {}
for i in range(1220):
if i in scan_objs:
delta = i - lastseen
for stk,name,what in scan_objs[i]:
print(f'{i:04d} {stk:60} | {name:30} | {what}')
deltas[name] = delta
lastseen = i
else:
print(f'{i:04d} -----')
def nice_param(p):
# param names don't matter
np = {'_type': p['_type'], 'flags': p['flags']}
if 'myname' in p and p['myname']:
np['myname'] = p['myname']
delta = deltas[p['_name']]
if p['_name'] == '_optlstCmdLine099':
delta -= 1 # hack to account for the weird 'progress' one
if delta > 1:
np['_idskip'] = delta - 1
if p['_type'] == 'FTypeCreator':
np['target'] = p['fc']
np['is_creator'] = p['iscreator']
elif p['_type'] == 'FilePath':
np['fflags'] = p['fflags']
np['default'] = p['defaultstr']
np['target'] = p['filename']
np['max_length'] = p['maxlen']
elif p['_type'] == 'Number':
np['target'] = p['num']
np['minimum'] = p['lo']
np['maximum'] = p['hi']
np['byte_size'] = p['size']
np['fit'] = p['fit']
elif p['_type'] in ('String','Id','Sym'):
np['max_length'] = p['maxlen']
np['pascal'] = (p['pstring'] != 0)
np['target'] = p['str']
elif p['_type'] == 'OnOff':
np['target'] = p['var']
elif p['_type'] == 'OffOn':
np['target'] = p['var']
elif p['_type'] == 'Mask':
np['target'] = p['num']
np['byte_size'] = p['size']
np['or_mask'] = p['ormask']
np['and_mask'] = p['andmask']
elif p['_type'] == 'Toggle':
np['target'] = p['num']
np['byte_size'] = p['size']
np['mask'] = p['mask']
elif p['_type'] == 'Set':
np['target'] = p['num']
np['value'] = p['value']
np['byte_size'] = p['size']
elif p['_type'] == 'SetString':
np['target'] = p['var']
np['value'] = p['value']
np['pascal'] = (p['pstring'] != 0)
elif p['_type'] == 'Generic':
np['function'] = p['parse']
np['arg'] = p['var']
np['help'] = p['help']
elif p['_type'] == 'IfArg':
np['help_a'] = p['helpa']
np['help_n'] = p['helpn']
np['if_arg'] = [nice_param(sp) for sp in p['parg']]
np['if_no_arg'] = [nice_param(sp) for sp in p['pnone']]
elif p['_type'] == 'Setting':
np['function'] = p['parse']
np['value_name'] = p['valuename']
return np
def nice_option(o, exclusive_parent=False):
no = {'names': o['names'], 'tools': [], 'params': [nice_param(p) for p in o['param']]}
if 'help' in o:
no['help'] = o['help']
if o['_name'] == '_optlstCmdLine_progress':
no['custom_name'] = 'progress' # lol hack
if (o['avail'] & 0x1) != 0: no['global'] = True
if (o['avail'] & 0x2) != 0: no['sticky'] = True
if (o['avail'] & 0x4) != 0: no['cased'] = True
if (o['avail'] & 0x8) != 0: no['obsolete'] = True
if (o['avail'] & 0x10) != 0: no['substituted'] = True
if (o['avail'] & 0x20) != 0: no['deprecated'] = True
if (o['avail'] & 0x100) != 0: no['tools'].append('compiler')
if (o['avail'] & 0x40) != 0: no['tools'].append('linker')
if (o['avail'] & 0x80) != 0: no['tools'].append('disassembler')
if (o['avail'] & 0x200) != 0: raise ValueError('fucked')
if (o['avail'] & 0x400) != 0: raise ValueError('fucked')
if (o['avail'] & 0x800) != 0: no['ignored'] = True
if (o['avail'] & 0x1000) != 0: no['secret'] = True
if (o['avail'] & 0x2000) != 0: no['hide_default'] = True
if (o['avail'] & 0x4000) != 0: no['compatibility'] = True
if (o['avail'] & 0x20000) != 0: no['only_once'] = True
if (o['avail'] & 0x80000) != 0: no['warning'] = True
if (o['avail'] & 0x100000) != 0: no['can_be_negated'] = True
if (o['avail'] & 0x200000) != 0: no['can_be_negated_2'] = True
if (o['avail'] & 0x400000) != 0: no['can_be_negated_3'] = True
if (o['avail'] & 0x800000) != 0: no['meaningless'] = True
if (o['avail'] & 0x8000) != 0:
subflags = o['sub']['flags']
obj_comp = (o['avail'] & 0x100) != 0
obj_link = (o['avail'] & 0x40) != 0
obj_disasm = (o['avail'] & 0x80) != 0
sublist_comp = (subflags & 0x100) != 0
sublist_link = (subflags & 0x200) != 0
sublist_disasm = (subflags & 0x400) != 0
assert(obj_comp == sublist_comp)
assert(obj_link == sublist_link)
assert(obj_disasm == sublist_disasm)
excl = False
if (o['avail'] & 0x10000) != 0:
no['sub_options_optional'] = True
if (subflags & 2) != 0:
no['sub_options_allow_unknowns'] = True
if (subflags & 1) != 0:
no['sub_options_exclusive'] = True
excl = True
assert('help' not in o['sub'])
no['sub_options'] = [nice_option(so, excl) for so in o['sub']['list']]
else:
assert('sub' not in o)
if exclusive_parent:
# conflicts flag should be set
assert((o['avail'] & 0x40000) == 0x40000)
assert('help' not in o['conflicts'])
else:
if (o['avail'] & 0x40000) == 0x40000:
no['conflict_group'] = o['conflicts']['_name'][1:].replace('_conflicts', '')
return no
if len(sys.argv) > 2:
nice_optlsts_map = {}
for optlst in optlsts:
#print('---' + optlst['_name'])
root_list = {'name': optlst['_name'].replace('_optlst', ''), 'tools': [], 'help': optlst['help'], 'options': []}
if (optlst['flags'] & 0x100) != 0: root_list['tools'].append('compiler')
if (optlst['flags'] & 0x200) != 0: root_list['tools'].append('linker')
if (optlst['flags'] & 0x400) != 0: root_list['tools'].append('disassembler')
for opt in optlst['list']:
#print('+' + opt['names'])
root_list['options'].append(nice_option(opt))
nice_optlsts_map[root_list['name']] = root_list
nice_optlsts = [
nice_optlsts_map['CmdLine'],
nice_optlsts_map['CmdLineCompiler'],
nice_optlsts_map['CmdLineLinker'],
nice_optlsts_map['Debugging'],
nice_optlsts_map['FrontEndC'],
nice_optlsts_map['WarningC'],
nice_optlsts_map['Optimizer'],
nice_optlsts_map['BackEnd'],
nice_optlsts_map['Project'],
nice_optlsts_map['Linker'],
nice_optlsts_map['Dumper']
]
with open(sys.argv[2], 'w') as f:
json.dump(nice_optlsts, f, indent=4)