pyinstxtractor/decompyle_with_pycdc.py
SemteulGaram 3f156cebc8 decompyle_with_pycdc
Co-authored-by: Copilot <copilot@github.com>
2026-06-09 20:25:59 +09:00

106 lines
No EOL
3.4 KiB
Python

#!/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 <extracted_dir> [--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()