Aayco

dis

Jun 20th, 2025
19
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Python 6.55 KB | None | 0 0
  1. import argparse
  2. import marshal
  3. import os
  4. import types
  5. import dis
  6. import json
  7. import sys
  8. def load_pyc(file_path):
  9.     with open(file_path, "rb") as f:
  10.         header_size = 16  # Python 3.13
  11.         f.read(header_size)
  12.         return marshal.load(f)
  13. def extract_functions(code_obj, parent=""):
  14.     functions = {}
  15.     for const in code_obj.co_consts:
  16.         if isinstance(const, types.CodeType):
  17.             name = const.co_name
  18.             full_name = f"{parent}.{name}" if parent else name
  19.             if full_name not in functions:
  20.                 functions[full_name] = const
  21.             functions.update(extract_functions(const, parent=full_name))
  22.     return functions
  23. def save_functions(functions, output_dir):
  24.     for name, code in functions.items():
  25.         safe_name = name.replace(".", "_")
  26.         file_path = os.path.join(output_dir, f"{safe_name}.dis")
  27.         with open(file_path, "w", encoding="utf-8") as f:
  28.             try:
  29.                 f.write(dis.code_info(code) + "\n\n")
  30.                 dis.dis(code, file=f)
  31.             except Exception as e:
  32.                 f.write(f"Disassembly error: {e}")
  33. def extract_imports_vars_classes(code_obj):
  34.     imports = set()
  35.     variables = []
  36.     classes = []
  37.     for instr in dis.get_instructions(code_obj):
  38.         if instr.opname in ("IMPORT_NAME", "IMPORT_FROM"):
  39.             imports.add(instr.argval)
  40.         elif instr.opname == "STORE_NAME":
  41.             variables.append({"name": instr.argval, "line": instr.starts_line or -1, "value": "<?>"} )
  42.         elif instr.opname == "LOAD_BUILD_CLASS":
  43.             idx = instr.offset
  44.             next_instrs = list(dis.get_instructions(code_obj))[idx:idx+5]
  45.             for ni in next_instrs:
  46.                 if ni.opname == "LOAD_CONST" and isinstance(ni.argval, str):
  47.                     classes.append({"name": ni.argval, "line": ni.starts_line or -1})
  48.                     break
  49.     return list(imports), variables, classes
  50. def extract_function_defs_with_consts(code_obj):
  51.     functions = []
  52.     for const in code_obj.co_consts:
  53.         if isinstance(const, types.CodeType):
  54.             if const.co_name != "<module>":
  55.                 argcount = const.co_argcount
  56.                 argnames = const.co_varnames[:argcount]
  57.                 docstring = ""
  58.                 if const.co_consts and isinstance(const.co_consts[0], str):
  59.                     docstring = const.co_consts[0]
  60.                 literals = [c for c in const.co_consts if isinstance(c, (str, int, float, bool))]
  61.                 functions.append({
  62.                     "name": const.co_name,
  63.                     "args": list(argnames),
  64.                     "line": const.co_firstlineno,
  65.                     "doc": docstring,
  66.                     "literals": list(set(literals) - {docstring}),
  67.                 })
  68.             functions += extract_function_defs_with_consts(const)
  69.     return functions
  70. def detect_if_main(code_obj):
  71.     for instr in dis.get_instructions(code_obj):
  72.         if instr.opname == "LOAD_NAME" and instr.argval == "__name__":
  73.             return True
  74.     return False
  75. def save_json(imports, variables, classes, output_dir):
  76.     with open(os.path.join(output_dir, "imports.json"), "w", encoding="utf-8") as f:
  77.         json.dump(sorted(imports), f, indent=2)
  78.     with open(os.path.join(output_dir, "vars.json"), "w", encoding="utf-8") as f:
  79.         json.dump(variables, f, indent=2)
  80.     with open(os.path.join(output_dir, "classes.json"), "w", encoding="utf-8") as f:
  81.         json.dump(classes, f, indent=2)
  82. def generate_stub(imports, variables, classes, functions, has_main, output_path, include_lines=True):
  83.     with open(output_path, "w", encoding="utf-8") as f:
  84.         for imp in sorted(imports):
  85.             f.write(f"import {imp}\n")
  86.         f.write("\n")
  87.         for var in variables:
  88.             line = f"# line {var['line']} " if include_lines and var['line'] > 0 else ""
  89.             f.write(f"{line}{var['name']} = {var['value']}\n")
  90.         f.write("\n")
  91.         for cls in classes:
  92.             line = f"# line {cls['line']} " if include_lines and cls['line'] > 0 else ""
  93.             f.write(f"{line}class {cls['name']}:\n    \"\"\"TODO: Add class documentation\"\"\"\n    pass\n\n")
  94.         for func in functions:
  95.             args = ", ".join(func["args"])
  96.             line = f"# line {func['line']} " if include_lines and func['line'] > 0 else ""
  97.             f.write(f"{line}def {func['name']}({args}):\n")
  98.             if func["doc"]:
  99.                 f.write(f"    \"\"\"{func['doc']}\"\"\"\n")
  100.             else:
  101.                 f.write(f"    \"\"\"TODO: Add function documentation\"\"\"\n")
  102.             f.write(f"    pass\n\n")
  103.         if has_main:
  104.             f.write('if __name__ == "__main__":\n    pass\n')
  105. def generate_type_stub(functions, classes, output_path):
  106.     with open(output_path, "w", encoding="utf-8") as f:
  107.         for cls in classes:
  108.             f.write(f"class {cls['name']}:\n    ...\n\n")
  109.         for func in functions:
  110.             args = ", ".join(func["args"])
  111.             f.write(f"def {func['name']}({args}) -> None: ...\n")
  112. def parse_args():
  113.     parser = argparse.ArgumentParser(description="Disassemble a .pyc file and extract components.")
  114.     parser.add_argument("pyc_path", help="Path to the .pyc file")
  115.     parser.add_argument("--stub", action="store_true", help="Generate a Python stub file")
  116.     parser.add_argument("--json", action="store_true", help="Export imports/vars/classes as JSON")
  117.     parser.add_argument("--filter", help="Only include functions containing this string")
  118.     return parser.parse_args()
  119. def main():
  120.     args = parse_args()
  121.     # Use same folder as the .pyc file
  122.     output_dir = os.path.dirname(os.path.abspath(args.pyc_path))
  123.     os.makedirs(output_dir, exist_ok=True)
  124.     code = load_pyc(args.pyc_path)
  125.     functions = extract_functions(code)
  126.     if args.filter:
  127.         functions = {name: fn for name, fn in functions.items() if args.filter in name}
  128.     save_functions(functions, output_dir=output_dir)
  129.     imports, variables, classes = extract_imports_vars_classes(code)
  130.     function_defs = extract_function_defs_with_consts(code)
  131.     has_main = detect_if_main(code)
  132.     if args.json:
  133.         save_json(imports, variables, classes, output_dir=output_dir)
  134.     if args.stub:
  135.         generate_stub(imports, variables, classes, function_defs, has_main,
  136.                       output_path=os.path.join(output_dir, "decompiled_stub.py"))
  137.         generate_type_stub(function_defs, classes,
  138.                            output_path=os.path.join(output_dir, "decompiled_stub.pyi"))
  139. if __name__ == "__main__":
  140.     main()
Advertisement
Add Comment
Please, Sign In to add comment