diff options
Diffstat (limited to '')
-rw-r--r-- | opt_decomp.py | 394 |
1 files changed, 394 insertions, 0 deletions
diff --git a/opt_decomp.py b/opt_decomp.py new file mode 100644 index 0000000..a6e612b --- /dev/null +++ b/opt_decomp.py @@ -0,0 +1,394 @@ +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) |