forked from mirrors/pyinstxtractor
decompyle_with_pycdc
Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
parent
af384e2eec
commit
3f156cebc8
1 changed files with 106 additions and 0 deletions
106
decompyle_with_pycdc.py
Normal file
106
decompyle_with_pycdc.py
Normal file
|
|
@ -0,0 +1,106 @@
|
|||
#!/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()
|
||||
Loading…
Add table
Add a link
Reference in a new issue