You've already forked python-uncompyle6
mirror of
https://github.com/rocky/python-uncompyle6.git
synced 2025-08-04 01:09:52 +08:00
Part of a much needed cleanup. Move semantics routines into its own
directory. Move out lots of stuff from __init__ to their own files. Add file loading tests. Document AST handling a tad more complete.
This commit is contained in:
200
uncompyle6/main.py
Normal file
200
uncompyle6/main.py
Normal file
@@ -0,0 +1,200 @@
|
||||
from __future__ import print_function
|
||||
import os, sys, types
|
||||
|
||||
from uncompyle6.disas import check_object_path
|
||||
from uncompyle6 import verify
|
||||
from uncompyle6.semantics import pysource
|
||||
|
||||
from uncompyle6.scanner import get_scanner
|
||||
from uncompyle6.load import load_module
|
||||
|
||||
# FIXME: remove duplicate code from deparse_code
|
||||
def uncompyle(version, co, out=None, showasm=False, showast=False):
|
||||
"""
|
||||
disassembles and deparses a given code block 'co'
|
||||
"""
|
||||
|
||||
assert isinstance(co, types.CodeType)
|
||||
|
||||
# store final output stream for case of error
|
||||
real_out = out or sys.stdout
|
||||
print('# Python %s' % version, file=real_out)
|
||||
if co.co_filename:
|
||||
print('# Embedded file name: %s' % co.co_filename,
|
||||
file=real_out)
|
||||
|
||||
scanner = get_scanner(version)
|
||||
tokens, customize = scanner.disassemble(co)
|
||||
|
||||
if showasm:
|
||||
for t in tokens:
|
||||
print(t, file=real_out)
|
||||
print(file=out)
|
||||
|
||||
# Build AST from disassembly.
|
||||
walk = pysource.Walker(version, out, scanner, showast=showast)
|
||||
try:
|
||||
ast = walk.build_ast(tokens, customize)
|
||||
except pysource.ParserError as e : # parser failed, dump disassembly
|
||||
print(e, file=real_out)
|
||||
raise
|
||||
del tokens # save memory
|
||||
|
||||
# convert leading '__doc__ = "..." into doc string
|
||||
assert ast == 'stmts'
|
||||
try:
|
||||
if ast[0][0] == pysource.ASSIGN_DOC_STRING(co.co_consts[0]):
|
||||
walk.print_docstring('', co.co_consts[0])
|
||||
del ast[0]
|
||||
if ast[-1] == pysource.RETURN_NONE:
|
||||
ast.pop() # remove last node
|
||||
# todo: if empty, add 'pass'
|
||||
except:
|
||||
pass
|
||||
walk.mod_globs = pysource.find_globals(ast, set())
|
||||
walk.gen_source(ast, customize)
|
||||
for g in walk.mod_globs:
|
||||
walk.write('global %s ## Warning: Unused global' % g)
|
||||
if walk.ERROR:
|
||||
raise walk.ERROR
|
||||
|
||||
def uncompyle_file(filename, outstream=None, showasm=False, showast=False):
|
||||
"""
|
||||
decompile Python byte-code file (.pyc)
|
||||
"""
|
||||
check_object_path(filename)
|
||||
version, magic_int, co = load_module(filename)
|
||||
if type(co) == list:
|
||||
for con in co:
|
||||
uncompyle(version, con, outstream, showasm, showast)
|
||||
else:
|
||||
uncompyle(version, co, outstream, showasm, showast)
|
||||
co = None
|
||||
|
||||
def main(in_base, out_base, files, codes, outfile=None,
|
||||
showasm=False, showast=False, do_verify=False):
|
||||
'''
|
||||
in_base base directory for input files
|
||||
out_base base directory for output files (ignored when
|
||||
files list of filenames to be uncompyled (relative to src_base)
|
||||
outfile write output to this filename (overwrites out_base)
|
||||
|
||||
For redirecting output to
|
||||
- <filename> outfile=<filename> (out_base is ignored)
|
||||
- files below out_base out_base=...
|
||||
- stdout out_base=None, outfile=None
|
||||
'''
|
||||
def _get_outstream(outfile):
|
||||
dir = os.path.dirname(outfile)
|
||||
failed_file = outfile + '_failed'
|
||||
if os.path.exists(failed_file):
|
||||
os.remove(failed_file)
|
||||
try:
|
||||
os.makedirs(dir)
|
||||
except OSError:
|
||||
pass
|
||||
return open(outfile, 'w')
|
||||
|
||||
of = outfile
|
||||
tot_files = okay_files = failed_files = verify_failed_files = 0
|
||||
|
||||
# for code in codes:
|
||||
# version = sys.version[:3] # "2.5"
|
||||
# with open(code, "r") as f:
|
||||
# co = compile(f.read(), "", "exec")
|
||||
# uncompyle(sys.version[:3], co, sys.stdout, showasm=showasm, showast=showast)
|
||||
|
||||
for filename in files:
|
||||
infile = os.path.join(in_base, filename)
|
||||
# print (infile, file=sys.stderr)
|
||||
|
||||
if of: # outfile was given as parameter
|
||||
outstream = _get_outstream(outfile)
|
||||
elif out_base is None:
|
||||
outstream = sys.stdout
|
||||
else:
|
||||
outfile = os.path.join(out_base, filename) + '_dis'
|
||||
outstream = _get_outstream(outfile)
|
||||
# print(outfile, file=sys.stderr)
|
||||
|
||||
# try to decomyple the input file
|
||||
try:
|
||||
uncompyle_file(infile, outstream, showasm, showast)
|
||||
tot_files += 1
|
||||
except ValueError as e:
|
||||
sys.stderr.write("\n# %s" % e)
|
||||
failed_files += 1
|
||||
except KeyboardInterrupt:
|
||||
if outfile:
|
||||
outstream.close()
|
||||
os.remove(outfile)
|
||||
sys.stderr.write("\nLast file: %s " % (infile))
|
||||
raise
|
||||
except:
|
||||
failed_files += 1
|
||||
if outfile:
|
||||
outstream.close()
|
||||
os.rename(outfile, outfile + '_failed')
|
||||
else:
|
||||
sys.stderr.write("\n# Can't uncompyle %s\n" % infile)
|
||||
else: # uncompyle successfull
|
||||
if outfile:
|
||||
outstream.close()
|
||||
if do_verify:
|
||||
try:
|
||||
msg = verify.compare_code_with_srcfile(infile, outfile)
|
||||
if not outfile:
|
||||
if not msg:
|
||||
print('\n# okay decompyling %s' % infile)
|
||||
okay_files += 1
|
||||
else:
|
||||
print('\n# %s\n\t%s', infile, msg)
|
||||
except verify.VerifyCmpError as e:
|
||||
verify_failed_files += 1
|
||||
os.rename(outfile, outfile + '_unverified')
|
||||
if not outfile:
|
||||
print("### Error Verifiying %s" % filename, file=sys.stderr)
|
||||
print(e, file=sys.stderr)
|
||||
else:
|
||||
okay_files += 1
|
||||
if not outfile:
|
||||
mess = '\n# okay decompyling'
|
||||
# mem_usage = __memUsage()
|
||||
print(mess, infile)
|
||||
if outfile:
|
||||
sys.stdout.write("%s\r" %
|
||||
status_msg(do_verify, tot_files, okay_files, failed_files, verify_failed_files))
|
||||
sys.stdout.flush()
|
||||
if outfile:
|
||||
sys.stdout.write("\n")
|
||||
sys.stdout.flush()
|
||||
return (tot_files, okay_files, failed_files, verify_failed_files)
|
||||
|
||||
|
||||
# ---- main ----
|
||||
|
||||
if sys.platform.startswith('linux') and os.uname()[2][:2] in ['2.', '3.', '4.']:
|
||||
def __memUsage():
|
||||
mi = open('/proc/self/stat', 'r')
|
||||
mu = mi.readline().split()[22]
|
||||
mi.close()
|
||||
return int(mu) / 1000000
|
||||
else:
|
||||
def __memUsage():
|
||||
return ''
|
||||
|
||||
def status_msg(do_verify, tot_files, okay_files, failed_files,
|
||||
verify_failed_files):
|
||||
if tot_files == 1:
|
||||
if failed_files:
|
||||
return "decompile failed"
|
||||
elif verify_failed_files:
|
||||
return "decompile verify failed"
|
||||
else:
|
||||
return "Successfully decompiled file"
|
||||
pass
|
||||
pass
|
||||
mess = "decompiled %i files: %i okay, %i failed" % (tot_files, okay_files, failed_files)
|
||||
if do_verify:
|
||||
mess += (", %i verify failed" % verify_failed_files)
|
||||
return mess
|
Reference in New Issue
Block a user