Skip to main content

pyc_revive

.pyc → .py reconstructor. Pure stdlib. No uncompyle6. No pycdc. No xdis.

Handles every CPython from 2.0 through 3.15, with a bundled disassembler fallback for versions that don't match the running interpreter, and a portable Node.js + depyo path for the newest bytecode (3.14+).

install

pip install pyc-revive

The Python package is stdlib-only. On first run, pyc-revive checks for Node.js and depyo, and installs them if missing — no admin, no system changes, everything under %LOCALAPPDATA%\pyc_revive (or the equivalent user dir on non-Windows):

  • Node.js ≥ 20 via winget (user scope) if available, otherwise a portable ZIP extracted into the user dir. No MSI, no registry writes.
  • depyo via npm into a user-writable prefix (%LOCALAPPDATA%\pyc_revive\npm-global).
  • optional pip backends (pychd, byteripper, uncompyle6, decompyle3) installed individually with --user fallback when the system site isn't writable.

To run the installer explicitly:

pyc-revive --install-deps

To skip the first-run bootstrap entirely:

pyc-revive --no-bootstrap Backdoor.pyc

From a checkout (not pip-installed):

python -m pyc_revive my file.pyc
python -m pyc_revive ./dist/app.pyc --out ./revived.py
python -m pyc_revive --install-deps

Input handling: exact path, spaces with no quotes, basename search in cwd + all subfolders + parent folder.

versions

Every CPython magic from 2.0 through 3.15 resolves (pymagic.py: full table + nearest fallback + header sizes).

Three backends, one pipeline:

  • native — .pyc magic matches the running interpreter. stdlib marshal loads the code object, stdlib dis disassembles it. Zero dependencies.
  • pycdas — anything else 2.x–3.13. The bundled pycdas.exe disassembles the foreign version, pycdas_parser.py parses the dump into code trees, shim.py adapts them to the pipeline. Everything downstream (normalize, segment, translate, CFG, emit, verify×3) runs unchanged.
  • depyo — 3.14+. depyo_parser.py runs depyo --skip-source-gen --asm and parses the .pyasm dump; depyo_postprocess.py fixes depyo's mechanical emitter bugs (dropped statements, mangled imports, wrong keywords) using the pycdas pipeline's output as a repair source when both ran. When the pipeline fails to compile, depyo-direct produces working source from depyo's own decompiler.

Routing is by magic number, decided before any bytecode is touched: a foreign magic goes straight to the appropriate backend. marshal.loads can succeed on foreign payloads since the format is stable, and then stdlib dis mis-decodes the unknown opcodes and can take the whole process down instead of raising — so "try native, fall back on error" is not used for foreign files.

3.14 and 3.15 support

3.14 was the first foreign 3.11+ target exercised end-to-end, and it needed real work beyond a new opcode table.

Translator additions:

  • LOAD_SMALL_INT, LOAD_FAST_BORROW, LOAD_FAST_BORROW_LOAD_FAST_BORROW (double 4-bit index, high nibble first), LOAD_SPECIAL, FORMAT_SIMPLE, TO_BOOL (transparent), CALL_KW (kwargs split like CALL_FUNCTION_KW, NULL-skipped).
  • BINARY_OP oparg ≥ 26 (3.14 renumbers the table; 26 = NB_SUBSCR, 27 = inplace).
  • KW_NAMES — pycdas 3.13 sometimes prints it as LOAD_CONST <i> // Value: ('a', 'b'), a tuple of key names. shim.py rewrites the opname when the argval is a string tuple immediately followed by a CALL_KW, so the translator's kwarg split runs.

Parser additions (pycdas_parser.py):

  • [Locals+Names] sections read into co_varnames.
  • [Exception Table] entries (S to E -> T [depth] (lasti?)) parsed and re-encoded to co_exceptiontable bytes on the shim, so the shared try/with-region machinery runs unchanged.

Shim additions (shim.py):

  • 3.14 conditional-branch targets from the bundled pycdas land 2 bytes low (it suppresses a hidden CACHE but forgets it in target math — proven three ways: dead else bodies, inverted with-cleanup, stack-underflow RETURN). Repaired when the fingerprint is exact: 3.14+ dump, NOT_TAKEN at branch+4, target+2 is a real offset.
  • 3.14 dropped BEFORE_WITH for an inline prologue (COPY, LOAD_SPECIAL, SWAP, SWAP, LOAD_SPECIAL, CALL); the shim folds it back to one synthetic BEFORE_WITH.
  • LOAD_FAST_BORROW index resolution, including the pair form.

Region detection:

  • with-bodies are scoped by their exception-table range (fallthrough code after the body dedents instead of hiding under with).
  • try interiors stop at the body end (post-try joins don't indent under try:).
  • post-try joins stranded textually before out-of-line handlers are deferred past the except clause.
  • RERAISE-ended handlers that pair with nothing are skipped as implicit-cleanup residue.
  • duplicated finally bodies pair by twin match: an unpaired RERAISE-ended handler whose leading statements duplicate a post-try block's leading statements is the exceptional finally copy (tails decide the split — [.., return] vs [.., raise] means the return is post-statement). The normal copy splits into finally: + rest.

3.15 goes through the same depyo path. depyo's unresolved name masks (##NAME_N##, ##ERROR##, __common_const_N__) are collapsed or substituted in depyo_postprocess.py; stdlib imports dropped by depyo are reinserted from a canonical table.

pipeline

  1. find file (finder.py)
  2. detect version via magic (pymagic.py)
  3. load: native marshal, else pycdas / depyo backend (loader.py, pycdas_parser.py, depyo_parser.py, shim.py)
  4. disassemble + abstract (bytecode.py: Inst, EditableBytecode, opcode/oparg/value-stack/metadata tables)
  5. normalize / mask consts + names (normalizer.py)
  6. opcode-aware B/I/E segmentation (segmenter.py)
  7. statement translation (translator.py: stack machine, defaults recovery, lambdas inline, 2.x print/exec/class ops)
  8. control-flow graph from jumps (cfg.py: if/for/while/try/except-as (bare except: too)/with/finally, clauses + clusters, 3.11+ exception table, SETUP-region splits)
  9. emit + repairs (emitter.py: source-order splicing onto def/class stub lines, bases recovery, @decorator restore, global decls, cond chains, ternaries, nested if/else, loop ranges, break/continue recovery, indent + suite-guard passes)
  10. equivalence verify, 3 sweeps (verifier.py: recompile revived source, compare opcode structure, repair, repeat ×3)
  11. self-validation (autofix.py: compile the output, diagnose the first error, apply a targeted repair — orphan handler, empty suite, bad indent, orphan try: — until it compiles or the budget runs out; ships the closest parse)
  12. backend fallback (reconstructor.py: if the pipeline's output doesn't compile and depyo is available, depyo-direct runs with the pipeline's source as a repair companion, then post-processing fixes the mechanical bugs)

layout

pyc_revive/
  finder.py            file resolution
  pymagic.py           magic-number table + version detection
  loader.py            native marshal loading
  pycdas_parser.py     bundled-disassembler dump parser
  depyo_parser.py      depyo --asm dump parser
  depyo_postprocess.py repair pass for depyo output
  shim.py              foreign-version code trees behave like code objects
  bytecode.py          Inst + EditableBytecode
  normalizer.py        const/name masking
  segmenter.py         B/I/E opcode segmentation
  translator.py        opcode stream -> statements
  cfg.py               basic blocks + structured control flow
  emitter.py           source emission + structural repairs
  verifier.py          recompile + compare x3
  autofix.py           compile-repair loop
  reconstructor.py     orchestrator
  cli.py               interface
  bootstrap.py         first-run dep check
  install_deps.py      Node + depyo + pip backend installer
  deps.py              optional-backend reporter
  debug_log.py         verbose logging (PYC_REVIVE_VERBOSE=1)
  __main__.py
  pycdas.exe           bundled disassembler

known limits

  • inline comprehensions/genexps render as func(...) placeholders.
  • try bodies that fall through into always-run handlers keep flat order instead of guarded flow, unless an exception table is available (native 3.11+ and foreign dumps with [Exception Table] sections scope bodies, pair handlers, and defer stranded post-try joins properly).
  • exotic decorators reduce to bare defs; exotic call shapes degrade to commented disassembly rather than failing.
  • pre-3.8 default-argument recovery is best-effort (flag-style MAKE_FUNCTION from 3.8+ recovers exactly).
  • verifier scores dip across versions: opcode sets drift between releases, so cross-version similarity reads lower than same-version.
  • 3.14 FOR_ITER targets are untested (no loop in any 3.14 sample yet); if the bundled pycdas hides caches after it the same way it does for conditional branches, loop joins may land 2 bytes off.

license

MIT

Metadata

Release files for pyc-revive 1.0.2

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pyc-revive 1.0.2
File Size Uploaded
pyc_revive-1.0.2.tar.gz 154.8 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for pyc-revive 1.0.2
File Interpreter ABI Platform
pyc_revive-1.0.2-py3-none-any.whl Python 3 none any Details

Total release size: 313.5 kB

Release files / pyc_revive-1.0.2.tar.gz

Download URL pyc_revive-1.0.2.tar.gz
Size 154.8 kB
Tags Source
SHA-256 checksum
How to use checksums
d061aa6a75a3af318095b8551e86b1377ee6ad206e4ea729930fbc04a526fc1a
BLAKE2b-256 checksum
How to use checksums
10dc3c520c887427d578808087ccec853a0f46456d2446aa1bb49e598ecff4ec
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release files / pyc_revive-1.0.2-py3-none-any.whl

Download URL pyc_revive-1.0.2-py3-none-any.whl
Size 158.7 kB
Tags Python 3
SHA-256 checksum
How to use checksums
8904193128ba97a3a44d1081cf7ea47bd79d683842431ffcd2041627f54be231
BLAKE2b-256 checksum
How to use checksums
fcc7603b4846070a40d22d8637c174a1a236cd09c3933ab1829ce1e4309d4262
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
No
Uploaded via twine/7.0.0 CPython/3.11.2

Release history Release notifications | RSS feed

1.0.3

2 release files

This release

1.0.2 This release

2 release files

1.0.1

2 release files

1.0.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page