blob: a4a010bd1a3bee0a5c54eeead0032e1f6b7743a5 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
import apk_utils
import glob
import optparse
import os
import shutil
import sys
import utils
USAGE = 'usage: %prog [options] <apk>'
def parse_options():
parser = optparse.OptionParser(usage=USAGE)
parser.add_option('--dex',
help=('directory with dex files to use instead of those in '
+ 'the apk'),
default=None)
parser.add_option('--resources',
help=('pattern that matches resources to use instead of '
+ 'those in the apk'),
default=None)
parser.add_option('--out',
help='output file (default ./$(basename <apk>))',
default=None)
parser.add_option('--keystore',
help='keystore file (default ~/.android/app.keystore)',
default=None)
parser.add_option('--install',
help='install the generated apk with adb options -t -r -d',
default=False,
action='store_true')
parser.add_option('--adb-options',
help='additional adb options when running adb',
default=None)
parser.add_option('--quiet',
help='disable verbose logging',
default=False)
(options, args) = parser.parse_args()
if len(args) != 1:
parser.error('Expected <apk> argument, got: ' + ' '.join(args))
apk = args[0]
return (options, apk)
def repack(apk, processed_out, resources, temp, quiet, logging):
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
if not processed_out:
utils.Print('Using original APK as is', quiet=quiet)
return processed_apk
utils.Print(
'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
# Delete original dex files in APK.
with utils.ChangedWorkingDirectory(temp, quiet=quiet):
cmd = ['zip', '-d', 'processed.apk', '*.dex']
utils.RunCmd(cmd, quiet=quiet, logging=logging)
# Unzip the jar or zip file into `temp`.
if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
cmd = ['unzip', processed_out, '-d', temp]
if quiet:
cmd.insert(1, '-q')
utils.RunCmd(cmd, quiet=quiet, logging=logging)
processed_out = temp
# Insert the new dex and resource files from `processed_out` into the APK.
with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
dex_files = glob.glob('*.dex')
resource_files = glob.glob(resources) if resources else []
cmd = ['zip', '-u', '-9', processed_apk] + dex_files + resource_files
utils.RunCmd(cmd, quiet=quiet, logging=logging)
return processed_apk
def sign(unsigned_apk, keystore, temp, quiet, logging):
signed_apk = os.path.join(temp, 'unaligned.apk')
return apk_utils.sign_with_apksigner(
unsigned_apk, signed_apk, keystore, quiet=quiet, logging=logging)
def align(signed_apk, temp, quiet, logging):
utils.Print('Aligning', quiet=quiet)
aligned_apk = os.path.join(temp, 'aligned.apk')
return apk_utils.align(signed_apk, aligned_apk)
def masseur(
apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
install=False, quiet=False, logging=True):
if not out:
out = os.path.basename(apk)
if not keystore:
keystore = apk_utils.default_keystore()
with utils.TempDir() as temp:
processed_apk = None
if dex:
processed_apk = repack(apk, dex, resources, temp, quiet, logging)
else:
utils.Print(
'Signing original APK without modifying dex files', quiet=quiet)
processed_apk = os.path.join(temp, 'processed.apk')
shutil.copyfile(apk, processed_apk)
signed_apk = sign(
processed_apk, keystore, temp, quiet=quiet, logging=logging)
aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
utils.Print('Writing result to {}'.format(out), quiet=quiet)
shutil.copyfile(aligned_apk, out)
if install:
adb_cmd = ['adb']
if adb_options:
adb_cmd.extend(
[option for option in adb_options.split(' ') if option])
adb_cmd.extend(['install', '-t', '-r', '-d', out]);
utils.RunCmd(adb_cmd, quiet=quiet, logging=logging)
def main():
(options, apk) = parse_options()
masseur(apk, **vars(options))
return 0
if __name__ == '__main__':
sys.exit(main())