#!/usr/bin/env python3 """ Recursively decompile every .pyc file inside an _extracted folder using pycdc, saving the results into a 'decompyle' folder with the same directory structure. Usage: python decompyle_with_pycdc.py [--pycdc PATH] [--out OUT_DIR] Examples: python decompyle_with_pycdc.py yourprogram.exe_extracted python decompyle_with_pycdc.py yourprogram.exe_extracted --pycdc Release\\pycdc.exe --out decompyle """ import argparse import subprocess import sys from pathlib import Path def find_pycdc(user_path: str | None) -> str: """Determine the path to the pycdc executable.""" if user_path: if Path(user_path).is_file(): return user_path sys.exit(f"[!] Could not find the specified pycdc: {user_path}") # Common candidate locations (Windows / Linux) candidates = [ "pycdc", "pycdc.exe", "Release/pycdc.exe", "Release\\pycdc.exe", "./pycdc", ] for c in candidates: p = Path(c) if p.is_file(): return str(p) # It may be on PATH, so return as-is (validated at run time) return "pycdc" def main(): parser = argparse.ArgumentParser( description="Recursively decompile .pyc files from an _extracted folder using pycdc" ) parser.add_argument("extracted_dir", help="pyinstxtractor output folder (_extracted)") parser.add_argument("--pycdc", default=None, help="Path to the pycdc executable") parser.add_argument("--out", default="decompyle", help="Output folder (default: decompyle)") args = parser.parse_args() src_root = Path(args.extracted_dir).resolve() if not src_root.is_dir(): sys.exit(f"[!] Not a directory: {src_root}") out_root = Path(args.out).resolve() out_root.mkdir(parents=True, exist_ok=True) pycdc = find_pycdc(args.pycdc) pyc_files = sorted(src_root.rglob("*.pyc")) if not pyc_files: sys.exit(f"[!] No .pyc files found in: {src_root}") print(f"[*] pycdc : {pycdc}") print(f"[*] Input dir : {src_root}") print(f"[*] Output dir : {out_root}") print(f"[*] Target files: {len(pyc_files)}\n") ok = 0 fail = 0 for pyc in pyc_files: rel = pyc.relative_to(src_root) out_path = (out_root / rel).with_suffix(".py") out_path.parent.mkdir(parents=True, exist_ok=True) try: result = subprocess.run( [pycdc, str(pyc)], capture_output=True, text=True, encoding="utf-8", errors="replace", ) except FileNotFoundError: sys.exit(f"[!] Failed to run pycdc. Check the path: {pycdc}") # pycdc writes the decompiled output to stdout and warnings/errors to stderr. out_path.write_text(result.stdout, encoding="utf-8") if result.returncode == 0 and result.stdout.strip(): ok += 1 print(f"[OK] {rel} -> {out_path.relative_to(out_root)}") else: fail += 1 print(f"[FAIL] {rel}") err = (result.stderr or "").strip() if err: # Show only the first line for brevity print(f" {err.splitlines()[0]}") print(f"\n[*] Done: {ok} succeeded, {fail} failed, {len(pyc_files)} total") if fail: print("[i] Failed files may be version mismatches or header-less entry-point .pyc files.") if __name__ == "__main__": main()