From eef2a005b568ed1523f0c7c5d43374d848b07261 Mon Sep 17 00:00:00 2001 From: louist103 <35883445+louist103@users.noreply.github.com> Date: Sat, 5 Sep 2020 10:18:02 -0400 Subject: Decomp Heishi4 All OK (#364) * Fixed upstream * Migrate data, fix header, and begin Init function * Lots of progress * Almost done * One non matching left * Decomp EnHeishi4 All OK * PR fixes * PR fixes --- diff.py | 510 ++++++++++++++++++++++------------------------------------------ 1 file changed, 171 insertions(+), 339 deletions(-) (limited to 'diff.py') diff --git a/diff.py b/diff.py index 013d93594..8dec9ef18 100755 --- a/diff.py +++ b/diff.py @@ -5,7 +5,6 @@ import os import ast import argparse import subprocess -import collections import difflib import string import itertools @@ -21,7 +20,7 @@ def fail(msg): MISSING_PREREQUISITES = ( "Missing prerequisite python module {}. " - "Run `python3 -m pip install --user colorama ansiwrap attrs watchdog python-Levenshtein cxxfilt` to install prerequisites (cxxfilt only needed with --source)." + "Run `python3 -m pip install --user colorama ansiwrap attrs watchdog python-Levenshtein` to install prerequisites (python-Levenshtein only needed for --algorithm=levenshtein)." ) try: @@ -50,21 +49,6 @@ parser.add_argument( action="store_true", help="Diff .o files rather than a whole binary. This makes it possible to see symbol names. (Recommended)", ) -parser.add_argument( - "-e", - dest="diff_elf_symbol", - help="Diff a given function in two ELFs, one being stripped and the other one non-stripped. Requires objdump from binutils 2.33+.", -) -parser.add_argument( - "--source", - action="store_true", - help="Show source code (if possible). Only works with -o and -e.", -) -parser.add_argument( - "--inlines", - action="store_true", - help="Show inline function calls (if possible). Only works with -o and -e.", -) parser.add_argument( "--base-asm", dest="base_asm", @@ -142,20 +126,11 @@ parser.add_argument( parser.add_argument( "--algorithm", dest="algorithm", - default="levenshtein", + default="difflib", choices=["levenshtein", "difflib"], help="Diff algorithm to use.", ) -parser.add_argument( - "--max-size", - "--max-lines", - dest="max_lines", - type=int, - default=1024, - help="The maximum length of the diff, in lines.", -) - # Project-specific flags, e.g. different versions/make arguments. if hasattr(diff_settings, "add_custom_arguments"): diff_settings.add_custom_arguments(parser) @@ -166,15 +141,13 @@ args = parser.parse_args() config = {} diff_settings.apply(config, args) -arch = config.get("arch", "mips") baseimg = config.get("baseimg", None) myimg = config.get("myimg", None) mapfile = config.get("mapfile", None) makeflags = config.get("makeflags", []) source_directories = config.get("source_directories", None) -objdump_executable = config.get("objdump_executable", None) -MAX_FUNCTION_SIZE_LINES = args.max_lines +MAX_FUNCTION_SIZE_LINES = 4096 MAX_FUNCTION_SIZE_BYTES = MAX_FUNCTION_SIZE_LINES * 4 COLOR_ROTATION = [ @@ -190,7 +163,7 @@ COLOR_ROTATION = [ ] BUFFER_CMD = ["tail", "-c", str(10 ** 9)] -LESS_CMD = ["less", "-SRic", "-#6"] +LESS_CMD = ["less", "-Ric"] DEBOUNCE_DELAY = 0.1 FS_WATCH_EXTENSIONS = [".c", ".h"] @@ -203,30 +176,25 @@ if args.algorithm == "levenshtein": except ModuleNotFoundError as e: fail(MISSING_PREREQUISITES.format(e.name)) -if args.source: - try: - import cxxfilt - except ModuleNotFoundError as e: - fail(MISSING_PREREQUISITES.format(e.name)) - -if objdump_executable is None: - for objdump_cand in ["mips-linux-gnu-objdump", "mips64-elf-objdump"]: - try: - subprocess.check_call( - [objdump_cand, "--version"], - stdout=subprocess.DEVNULL, - stderr=subprocess.DEVNULL, - ) - objdump_executable = objdump_cand - break - except subprocess.CalledProcessError: - pass - except FileNotFoundError: - pass +binutils_prefix = None -if not objdump_executable: +for binutils_cand in ["mips-linux-gnu-", "mips64-elf-"]: + try: + subprocess.check_call( + [binutils_cand + "objdump", "--version"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + ) + binutils_prefix = binutils_cand + break + except subprocess.CalledProcessError: + pass + except FileNotFoundError: + pass + +if not binutils_prefix: fail( - "Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist, or configure objdump_executable." + "Missing binutils; please ensure mips-linux-gnu-objdump or mips64-elf-objdump exist." ) @@ -242,10 +210,6 @@ def eval_int(expr, emsg=None): return None -def eval_line_num(expr): - return int(expr.strip().replace(":", ""), 16) - - def run_make(target, capture_output=False): if capture_output: return subprocess.run( @@ -271,26 +235,10 @@ def restrict_to_function(dump, fn_name): return "\n".join(out) -def maybe_get_objdump_source_flags(): - if not args.source: - return [] - - flags = [ - "--source", - "--source-comment=| ", - "-l", - ] - - if args.inlines: - flags.append("--inlines") - - return flags - - def run_objdump(cmd): flags, target, restrict = cmd out = subprocess.check_output( - [objdump_executable] + arch_flags + flags + [target], universal_newlines=True + [binutils_prefix + "objdump"] + flags + [target], universal_newlines=True ) if restrict is not None: return restrict_to_function(out, restrict) @@ -343,36 +291,6 @@ def search_map_file(fn_name): return None, None -def dump_elf(): - if not baseimg or not myimg: - fail("Missing myimg/baseimg in config.") - if base_shift: - fail("--base-shift not compatible with -e") - - start_addr = eval_int(args.start, "Start address must be an integer expression.") - - if args.end is not None: - end_addr = eval_int(args.end, "End address must be an integer expression.") - else: - end_addr = start_addr + MAX_FUNCTION_SIZE_BYTES - - flags1 = [ - f"--start-address={start_addr}", - f"--stop-address={end_addr}", - ] - - flags2 = [ - f"--disassemble={args.diff_elf_symbol}", - ] - - objdump_flags = ["-drz", "-j", ".text"] - return ( - myimg, - (objdump_flags + flags1, baseimg, None), - (objdump_flags + flags2 + maybe_get_objdump_source_flags(), myimg, None), - ) - - def dump_objfile(): if base_shift: fail("--base-shift not compatible with -o") @@ -399,7 +317,7 @@ def dump_objfile(): return ( objfile, (objdump_flags, refobjfile, args.start), - (objdump_flags + maybe_get_objdump_source_flags(), objfile, args.start), + (objdump_flags, objfile, args.start), ) @@ -439,47 +357,29 @@ def ansi_ljust(s, width): return s -if arch == "mips": - re_int = re.compile(r"[0-9]+") - re_comment = re.compile(r"<.*?>") - re_reg = re.compile(r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|k[01]|fp|ra)\b") - re_sprel = re.compile(r"(?<=,)([0-9]+|0x[0-9a-f]+)\(sp\)") - re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") - re_imm = re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)") - forbidden = set(string.ascii_letters + "_") - arch_flags = ["-m", "mips:4300"] - branch_likely_instructions = { - "beql", - "bnel", - "beqzl", - "bnezl", - "bgezl", - "bgtzl", - "blezl", - "bltzl", - "bc1tl", - "bc1fl", - } - branch_instructions = branch_likely_instructions.union( - {"b", "beq", "bne", "beqz", "bnez", "bgez", "bgtz", "blez", "bltz", "bc1t", "bc1f"} - ) - instructions_with_address_immediates = branch_instructions.union({"jal", "j"}) -elif arch == "aarch64": - re_int = re.compile(r"[0-9]+") - re_comment = re.compile(r"(<.*?>|//.*$)") - # GPRs and FP registers: X0-X30, W0-W30, [DSHQ]0..31 - # The zero registers and SP should not be in this list. - re_reg = re.compile(r"\$?\b([dshq][12]?[0-9]|[dshq]3[01]|[xw][12]?[0-9]|[xw]30)\b") - re_sprel = re.compile(r"sp, #-?(0x[0-9a-fA-F]+|[0-9]+)\b") - re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") - re_imm = re.compile(r"(?") +re_regs = re.compile(r"\$?\b(a[0-3]|t[0-9]|s[0-8]|at|v[01]|f[12]?[0-9]|f3[01]|fp)\b") +re_sprel = re.compile(r",([1-9][0-9]*|0x[1-9a-f][0-9a-f]*)\(sp\)") +re_large_imm = re.compile(r"-?[1-9][0-9]{2,}|-?0x[0-9a-f]{3,}") +re_imm = re.compile(r"(\b|-)([0-9]+|0x[0-9a-fA-F]+)\b(?!\(sp)|%(lo|hi)\([^)]*\)") +forbidden = set(string.ascii_letters + "_") +branch_likely_instructions = { + "beql", + "bnel", + "beqzl", + "bnezl", + "bgezl", + "bgtzl", + "blezl", + "bltzl", + "bc1tl", + "bc1fl", +} +branch_instructions = branch_likely_instructions.union( + {"b", "beq", "bne", "beqz", "bnez", "bgez", "bgtz", "blez", "bltz", "bc1t", "bc1f"} +) +jump_instructions = branch_instructions.union({"jal", "j"}) def hexify_int(row, pat): @@ -512,18 +412,11 @@ def parse_relocated_line(line): return before, imm, after -def process_mips_reloc(row, prev): +def process_reloc(row, prev): before, imm, after = parse_relocated_line(prev) repl = row.split()[-1] if imm != "0": - # MIPS uses relocations with addends embedded in the code as immediates. - # If there is an immediate, show it as part of the relocation. Ideally - # we'd show this addend in both %lo/%hi, but annoyingly objdump's output - # doesn't include enough information to pair up %lo's and %hi's... - # TODO: handle unambiguous cases where all addends for a symbol are the - # same, or show "+???". - mnemonic = prev.split()[0] - if mnemonic in instructions_with_address_immediates and not imm.startswith("0x"): + if before.strip() == "jal" and not imm.startswith("0x"): imm = "0x" + imm repl += "+" + imm if int(imm, 0) > 0 else imm if "R_MIPS_LO16" in row: @@ -538,63 +431,38 @@ def process_mips_reloc(row, prev): return before + repl + after -def cleanup_whitespace(line): - return "".join(f"{o:<8s}" for o in line.strip().split("\t")) - - -Line = collections.namedtuple( - "Line", - [ - "mnemonic", - "diff_row", - "original", - "line_num", - "branch_target", - "source_lines", - "comment", - ], -) - - def process(lines): + mnemonics = [] + diff_rows = [] + rows_with_imms = [] skip_next = False - source_lines = [] + originals = [] + line_nums = [] + branch_targets = [] if not args.diff_obj: lines = lines[7:] if lines and not lines[-1]: lines.pop() - output = [] for row in lines: if args.diff_obj and (">:" in row or not row): continue - if args.source and (row and row[0] != " "): - source_lines.append(row) - continue - - if "R_AARCH64_" in row: - # TODO: handle relocation - continue - if "R_MIPS_" in row: # N.B. Don't transform the diff rows, they already ignore immediates - # if output[-1].diff_row != "": - # output[-1] = output[-1].replace(diff_row=process_mips_reloc(row, output[-1].row_with_imm)) - new_original = process_mips_reloc(row, output[-1].original) - output[-1] = output[-1]._replace(original=new_original) + # if diff_rows[-1] != '': + # diff_rows[-1] = process_reloc(row, rows_with_imms[-1]) + originals[-1] = process_reloc(row, originals[-1]) continue - m_comment = re.search(re_comment, row) - comment = m_comment[0] if m_comment else None - row = re.sub(re_comment, "", row) + row = re.sub(re_comments, "", row) row = row.rstrip() tabs = row.split("\t") row = "\t".join(tabs[2:]) line_num = tabs[0].strip() row_parts = row.split("\t", 1) mnemonic = row_parts[0].strip() - if mnemonic not in instructions_with_address_immediates: + if mnemonic not in jump_instructions: row = re.sub(re_int, lambda s: hexify_int(row, s), row) original = row if skip_next: @@ -603,45 +471,38 @@ def process(lines): mnemonic = "" if mnemonic in branch_likely_instructions: skip_next = True - row = re.sub(re_reg, "", row) - row = re.sub(re_sprel, "addr(sp)", row) + row = re.sub(re_regs, "", row) + row = re.sub(re_sprel, ",addr(sp)", row) row_with_imm = row - if mnemonic in instructions_with_address_immediates: + if mnemonic in jump_instructions: row = row.strip() row, _ = split_off_branch(row) row += "" else: - row = normalize_imms(row) + row = re.sub(re_imm, "", row) - branch_target = None + mnemonics.append(mnemonic) + rows_with_imms.append(row_with_imm) + diff_rows.append(row) + originals.append(original) + line_nums.append(line_num) if mnemonic in branch_instructions: target = row_parts[1].strip().split(",")[-1] if mnemonic in branch_likely_instructions: target = hex(int(target, 16) - 4)[2:] - branch_target = target.strip() - - output.append( - Line( - mnemonic=mnemonic, - diff_row=row, - original=original, - line_num=line_num, - branch_target=branch_target, - source_lines=source_lines, - comment=comment, - ) - ) - source_lines = [] - + branch_targets.append(target) + else: + branch_targets.append(None) if args.stop_jrra and mnemonic == "jr" and row_parts[1].strip() == "ra": break - # Cleanup whitespace, after relocation fixups have happened - output = [ - line._replace(original=cleanup_whitespace(line.original)) for line in output + # Cleanup whitespace + originals = [original.strip() for original in originals] + originals = [ + "".join(f"{o:<8s}" for o in original.split("\t")) for original in originals ] - - return output + # return diff_rows, diff_rows, line_nums + return mnemonics, diff_rows, originals, line_nums, branch_targets def format_single_line_diff(line1, line2, column_width): @@ -674,14 +535,10 @@ def normalize_imms(row): return re.sub(re_imm, "", row) -def normalize_stack(row): - return re.sub(re_sprel, "addr(sp)", row) - - def split_off_branch(line): parts = line.split(",") if len(parts) < 2: - parts = line.split(None, 1) + parts = line.split() off = len(line) - len(parts[-1]) return line[:off], line[off:] @@ -744,13 +601,20 @@ def diff_sequences(seq1, seq2): def do_diff(basedump, mydump): + asm_lines1 = basedump.split("\n") + asm_lines2 = mydump.split("\n") + output = [] # TODO: status line? # output.append(sha1sum(mydump)) - lines1 = process(basedump.split("\n")) - lines2 = process(mydump.split("\n")) + mnemonics1, asm_lines1, originals1, line_nums1, branch_targets1 = process( + asm_lines1 + ) + mnemonics2, asm_lines2, originals2, line_nums2, branch_targets2 = process( + asm_lines2 + ) sc1 = SymbolColorer(0) sc2 = SymbolColorer(0) @@ -762,160 +626,130 @@ def do_diff(basedump, mydump): bts2 = set() if args.show_branches: - for (lines, btset, sc) in [ - (lines1, bts1, sc5), - (lines2, bts2, sc6), + for (bts, btset, sc) in [ + (branch_targets1, bts1, sc5), + (branch_targets2, bts2, sc6), ]: - for line in lines: - bt = line.branch_target + for bt in bts: if bt is not None: btset.add(bt + ":") sc.color_symbol(bt + ":") - for (tag, i1, i2, j1, j2) in diff_sequences( - [line.mnemonic for line in lines1], [line.mnemonic for line in lines2] - ): - for line1, line2 in itertools.zip_longest(lines1[i1:i2], lines2[j1:j2]): + for (tag, i1, i2, j1, j2) in diff_sequences(mnemonics1, mnemonics2): + lines1 = asm_lines1[i1:i2] + lines2 = asm_lines2[j1:j2] + + for k, (line1, line2) in enumerate(itertools.zip_longest(lines1, lines2)): if tag == "replace": if line1 is None: tag = "insert" elif line2 is None: tag = "delete" - elif tag == "insert": - assert line1 is None - elif tag == "delete": - assert line2 is None + + try: + original1 = originals1[i1 + k] + line_num1 = line_nums1[i1 + k] + except: + original1 = "" + line_num1 = "" + try: + original2 = originals2[j1 + k] + line_num2 = line_nums2[j1 + k] + except: + original2 = "" + line_num2 = "" line_color1 = line_color2 = sym_color = Fore.RESET line_prefix = " " - if line1 and line2 and line1.diff_row == line2.diff_row: - if maybe_normalize_large_imms( - line1.original - ) == maybe_normalize_large_imms(line2.original): - out1 = line1.original - out2 = line2.original - elif line1.diff_row == "": - out1 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line1.original}" - out2 = f"{Style.BRIGHT}{Fore.LIGHTBLACK_EX}{line2.original}" + if line1 == line2: + if maybe_normalize_large_imms(original1) == maybe_normalize_large_imms( + original2 + ): + out1 = f"{original1}" + out2 = f"{original2}" + elif line1 == "": + out1 = f"{Style.DIM}{original1}" + out2 = f"{Style.DIM}{original2}" else: - mnemonic = line1.original.split()[0] - out1, out2 = line1.original, line2.original + mnemonic = original1.split()[0] + out1, out2 = original1, original2 branch1 = branch2 = "" - if mnemonic in instructions_with_address_immediates: - out1, branch1 = split_off_branch(line1.original) - out2, branch2 = split_off_branch(line2.original) + if mnemonic in jump_instructions: + out1, branch1 = split_off_branch(original1) + out2, branch2 = split_off_branch(original2) branchless1 = out1 branchless2 = out2 out1, out2 = color_imms(out1, out2) - - same_relative_target = False - if line1.branch_target is not None and line2.branch_target is not None: - relative_target1 = eval_line_num(line1.branch_target) - eval_line_num(line1.line_num) - relative_target2 = eval_line_num(line2.branch_target) - eval_line_num(line2.line_num) - same_relative_target = relative_target1 == relative_target2 - - if not same_relative_target: - branch1, branch2 = color_branch_imms(branch1, branch2) - + branch1, branch2 = color_branch_imms(branch1, branch2) out1 += branch1 out2 += branch2 if normalize_imms(branchless1) == normalize_imms(branchless2): - if not same_relative_target: - # only imms differences - sym_color = Fore.LIGHTBLUE_EX - line_prefix = "i" + # only imms differences + sym_color = Fore.LIGHTBLUE_EX + line_prefix = "i" else: + # regs differences and maybe imms as well + line_color1 = line_color2 = sym_color = Fore.YELLOW + line_prefix = "r" out1 = re.sub( - re_sprel, lambda s: sc3.color_symbol(s.group()), out1, + re_regs, lambda s: sc1.color_symbol(s.group()), out1 ) out2 = re.sub( - re_sprel, lambda s: sc4.color_symbol(s.group()), out2, + re_regs, lambda s: sc2.color_symbol(s.group()), out2 ) - if normalize_stack(branchless1) == normalize_stack(branchless2): - # only stack differences (luckily stack and imm - # differences can't be combined in MIPS, so we - # don't have to think about that case) - sym_color = Fore.YELLOW - line_prefix = "s" - else: - # regs differences and maybe imms as well - out1 = re.sub( - re_reg, lambda s: sc1.color_symbol(s.group()), out1 - ) - out2 = re.sub( - re_reg, lambda s: sc2.color_symbol(s.group()), out2 - ) - line_color1 = line_color2 = sym_color = Fore.YELLOW - line_prefix = "r" - elif line1 and line2: + out1 = re.sub( + re_sprel, lambda s: sc3.color_symbol(s.group()), out1 + ) + out2 = re.sub( + re_sprel, lambda s: sc4.color_symbol(s.group()), out2 + ) + out1 = f"{Fore.YELLOW}{out1}{Style.RESET_ALL}" + out2 = f"{Fore.YELLOW}{out2}{Style.RESET_ALL}" + elif tag in ["replace", "equal"]: line_prefix = "|" line_color1 = Fore.LIGHTBLUE_EX line_color2 = Fore.LIGHTBLUE_EX sym_color = Fore.LIGHTBLUE_EX - out1 = line1.original - out2 = line2.original - elif line1: + out1 = f"{Fore.LIGHTBLUE_EX}{original1}{Style.RESET_ALL}" + out2 = f"{Fore.LIGHTBLUE_EX}{original2}{Style.RESET_ALL}" + elif tag == "delete": line_prefix = "<" line_color1 = line_color2 = sym_color = Fore.RED - out1 = line1.original + out1 = f"{Fore.RED}{original1}{Style.RESET_ALL}" out2 = "" - elif line2: + elif tag == "insert": line_prefix = ">" line_color1 = line_color2 = sym_color = Fore.GREEN out1 = "" - out2 = line2.original + out2 = f"{Fore.GREEN}{original2}{Style.RESET_ALL}" in_arrow1 = " " in_arrow2 = " " out_arrow1 = "" out_arrow2 = "" - - if args.show_branches and line1: - if line1.line_num in bts1: - in_arrow1 = sc5.color_symbol(line1.line_num, "~>") + line_color1 - if line1.branch_target is not None: - out_arrow1 = " " + sc5.color_symbol(line1.branch_target + ":", "~>") - if args.show_branches and line2: - if line2.line_num in bts2: - in_arrow2 = sc6.color_symbol(line2.line_num, "~>") + line_color2 - if line2.branch_target is not None: - out_arrow2 = " " + sc6.color_symbol(line2.branch_target + ":", "~>") - - if args.source and line2 and line2.comment: - out2 += f" {line2.comment}" - - line_num1 = line1.line_num if line1 else "" - line_num2 = line2.line_num if line2 else "" - - out1 = f"{line_color1}{line_num1} {in_arrow1} {out1}{Style.RESET_ALL}{out_arrow1}" - out2 = f"{line_color2}{line_num2} {in_arrow2} {out2}{Style.RESET_ALL}{out_arrow2}" - mid = f"{sym_color}{line_prefix} " - - if line2: - for source_line in line2.source_lines: - color = Style.DIM - # File names and function names - if source_line and source_line[0] != "|": - color += Style.BRIGHT - # Function names - if source_line.endswith("():"): - # Underline. Colorama does not provide this feature, unfortunately. - color += "\u001b[4m" - try: - source_line = cxxfilt.demangle( - source_line[:-3], external_only=False - ) - except: - pass - output.append( - format_single_line_diff( - "", - f" {color}{source_line}{Style.RESET_ALL}", - args.column_width, - ) + line_num1 = line_num1 if out1 else "" + line_num2 = line_num2 if out2 else "" + + if args.show_branches and out1: + if line_num1 in bts1: + in_arrow1 = sc5.color_symbol(line_num1, "~>") + if branch_targets1[i1 + k] is not None: + out_arrow1 = " " + sc5.color_symbol( + branch_targets1[i1 + k] + ":", "~>" + ) + if args.show_branches and out2: + if line_num2 in bts2: + in_arrow2 = sc6.color_symbol(line_num2, "~>") + if branch_targets2[j1 + k] is not None: + out_arrow2 = " " + sc6.color_symbol( + branch_targets2[j1 + k] + ":", "~>" ) - output.append(format_single_line_diff(out1, mid + out2, args.column_width)) + if sym_color == line_color2: + line_color2 = "" + out1 = f"{line_color1}{line_num1} {in_arrow1} {out1}{Style.RESET_ALL}{out_arrow1}" + out2 = f"{sym_color}{line_prefix} {line_color2}{line_num2} {in_arrow2} {out2}{Style.RESET_ALL}{out_arrow2}" + output.append(format_single_line_diff(out1, out2, args.column_width)) return output[args.skip_lines :] @@ -1078,16 +912,14 @@ class Display: def main(): - if args.diff_elf_symbol: - make_target, basecmd, mycmd = dump_elf() - elif args.diff_obj: + if args.diff_obj: make_target, basecmd, mycmd = dump_objfile() else: make_target, basecmd, mycmd = dump_binary() if args.write_asm is not None: mydump = run_objdump(mycmd) - with open(args.write_asm, "w") as f: + with open(args.write_asm) as f: f.write(mydump) print(f"Wrote assembly to {args.write_asm}.") sys.exit(0) @@ -1148,4 +980,4 @@ def main(): display.terminate() -main() +main() \ No newline at end of file -- cgit v1.2.3