#!/usr/bin/env python3 import sys import pyperclip import subprocess import os from pathlib import Path import argparse import re import tempfile parser = argparse.ArgumentParser(description="Generates a context for decomp.me") parser.add_argument('file', help="Input file to preprocess") parser.add_argument('-f', type=str, dest='out_file', required=False, help='Output context file') parser.add_argument('-c', action=argparse.BooleanOptionalAction, dest='clipboard', required=False, help='Copy output to clipboard') parser.add_argument('-e', type=str, dest='encoding', required=False, default="utf-8", help='Input file encoding') parser.add_argument('-v', action=argparse.BooleanOptionalAction, dest='verbose', required=False, help='Verbose error output') args = parser.parse_args() CXX_FLAGS = [ '-nostdinc', '-Iinclude', '-Ilibs/c/include', '-Ilibs/cpp/include', '-Ilibs/nds/include' ] script_dir = Path(os.path.dirname(os.path.realpath(__file__))) root_dir = script_dir / ".." def eprint(*args, **kwargs): print(*args, file=sys.stderr, **kwargs) # Finds all lines starting with #include followed by <...> or "..." INCLUDE_REGEX = r'^\s*#\s*include\s*([<"][\S ]+[>"])\s*$' # Finds all line comments and multiline comments COMMENT_REGEX = r'\/\/.*$|\/\*(?:.|\r|\n)+?\*\/' with open(args.file, 'r', encoding=args.encoding) as f: contents = f.read() contents = re.sub(COMMENT_REGEX, '', contents, count=0, flags=re.MULTILINE) includes = re.findall(INCLUDE_REGEX, contents, flags=re.MULTILINE) _, suffix = os.path.splitext(args.file) with tempfile.NamedTemporaryFile(delete=True, suffix=suffix) as tmp_file: # Write includes for include in includes: tmp_file.write(f'#include {include}\n'.encode()) tmp_file.flush() # Run preprocessor try: ctx: str = subprocess.check_output([ 'gcc', '-E', '-P', '-fworking-directory', '-undef', '-dD', *CXX_FLAGS, tmp_file.name ], cwd=root_dir, encoding=args.encoding) except subprocess.CalledProcessError as e: eprint(f"Failed to preprocess '{args.file}'") if args.verbose: eprint(e) else: eprint("Use -v for more verbose error output") exit(1) lines = ctx.splitlines(True) for i in reversed(range(len(lines))): if lines[i].startswith('#define __cplusplus'): lines.pop(i) elif lines[i].startswith('#define __STDC_HOSTED__'): lines.pop(i) elif lines[i].startswith('#define __STDC__'): lines.pop(i) elif lines[i].startswith('#define __STDC_VERSION__'): lines.pop(i) ctx = ''.join(lines) if args.out_file: try: with open(args.out_file, "w") as file: file.write(ctx) except OSError as e: eprint(f"Failed to write file '{args.out_file}'") if args.verbose: eprint(e) else: eprint("Use -v for more verbose error output") exit(1) if args.clipboard: pyperclip.copy(ctx) eprint("Copied context to clipboard") if args.out_file is None and not args.clipboard: print(ctx)