1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
|
# Copyright 2015 Google Inc. All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import zipfile, traceback, argparse, collections
from . import parsedex
from .jvm import writeclass
from .mutf8 import decode
from .jvm.optimization import options
def read(fname, mode='rb'):
with open(fname, mode) as f:
return f.read()
def translate(data, opts, classes=None, errors=None, allowErrors=True):
dex = parsedex.DexFile(data)
classes = collections.OrderedDict() if classes is None else classes
errors = collections.OrderedDict() if errors is None else errors
for cls in dex.classes:
unicode_name = decode(cls.name) + '.class'
if unicode_name in classes or unicode_name in errors:
print('Warning, duplicate class name', unicode_name)
continue
try:
class_data = writeclass.toClassFile(cls, opts)
classes[unicode_name] = class_data
except Exception:
if not allowErrors:
raise
errors[unicode_name] = traceback.format_exc()
if not (len(classes) + len(errors)) % 1000:
print(len(classes) + len(errors), 'classes processed')
return classes, errors
def writeToJar(fname, classes):
with zipfile.ZipFile(fname, 'w') as out:
for unicode_name, data in classes.items():
# Don't bother compressing small files
compress_type = zipfile.ZIP_DEFLATED if len(data) > 10000 else zipfile.ZIP_STORED
info = zipfile.ZipInfo(unicode_name)
info.external_attr = 0o775 << 16 # set Unix file permissions
out.writestr(info, data, compress_type=compress_type)
def main():
parser = argparse.ArgumentParser(prog='enjarify', description='Translates Dalvik bytecode (.dex or .apk) to Java bytecode (.jar)')
parser.add_argument('inputfile')
parser.add_argument('-o', '--output', help='Output .jar file. Default is [input-filename]-enjarify.jar.')
parser.add_argument('-f', '--force', action='store_true', help='Force overwrite. If output file already exists, this option is required to overwrite.')
parser.add_argument('--fast', action='store_true', help='Speed up translation at the expense of generated bytecode being less readable.')
args = parser.parse_args()
dexs = []
if args.inputfile.lower().endswith('.apk'):
with zipfile.ZipFile(args.inputfile, 'r') as z:
for name in z.namelist():
if name.startswith('classes') and name.endswith('.dex'):
dexs.append(z.read(name))
else:
dexs.append(read(args.inputfile))
# Exclusive mode requires 3.3+, so provide helpful error in this case
if not args.force:
try:
FileExistsError
except NameError:
print('Overwrite protection requires Python 3.3+. Either pass -f or --force, or upgrade to a more recent version of Python. If you are using Pypy3 2.4, you need to switch to a nightly build or build from source. Or just pass -f.')
return
# Might as well open the output file early so we can detect existing file error
# before going to the trouble of translating everything
outname = args.output or args.inputfile.rpartition('/')[-1].rpartition('.')[0] + '-enjarify.jar'
try:
outfile = open(outname, mode=('wb' if args.force else 'xb'))
except FileExistsError:
print('Error, output file already exists and --force was not specified.')
print('To overwrite the output file, pass -f or --force.')
return
opts = options.NONE if args.fast else options.PRETTY
classes = collections.OrderedDict()
errors = collections.OrderedDict()
for data in dexs:
translate(data, opts=opts, classes=classes, errors=errors)
writeToJar(outfile, classes)
outfile.close()
print('Output written to', outname)
for name, error in sorted(errors.items()):
print(name, error)
print('{} classes translated successfully, {} classes had errors'.format(len(classes), len(errors)))
if __name__ == "__main__":
main()
|