blob: a4a010bd1a3bee0a5c54eeead0032e1f6b7743a5 [file] [log] [blame]
Ian Zernydcb172e2022-02-22 15:36:45 +01001#!/usr/bin/env python3
Mads Ager418d1ca2017-05-22 09:35:49 +02002# Copyright (c) 2017, the R8 project authors. Please see the AUTHORS file
3# for details. All rights reserved. Use of this source code is governed by a
4# BSD-style license that can be found in the LICENSE file.
5
Rico Wind0ed24cc2018-03-23 07:16:35 +01006import apk_utils
Mads Ager418d1ca2017-05-22 09:35:49 +02007import glob
8import optparse
9import os
10import shutil
Mads Ager418d1ca2017-05-22 09:35:49 +020011import sys
12import utils
13
14USAGE = 'usage: %prog [options] <apk>'
15
16def parse_options():
17 parser = optparse.OptionParser(usage=USAGE)
18 parser.add_option('--dex',
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010019 help=('directory with dex files to use instead of those in '
20 + 'the apk'),
21 default=None)
22 parser.add_option('--resources',
23 help=('pattern that matches resources to use instead of '
24 + 'those in the apk'),
Mads Ager418d1ca2017-05-22 09:35:49 +020025 default=None)
26 parser.add_option('--out',
27 help='output file (default ./$(basename <apk>))',
28 default=None)
29 parser.add_option('--keystore',
30 help='keystore file (default ~/.android/app.keystore)',
31 default=None)
Søren Gjessec4b3f612017-05-24 10:10:43 +020032 parser.add_option('--install',
33 help='install the generated apk with adb options -t -r -d',
34 default=False,
35 action='store_true')
Søren Gjesse12d41d42017-06-01 09:01:24 +020036 parser.add_option('--adb-options',
37 help='additional adb options when running adb',
38 default=None)
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010039 parser.add_option('--quiet',
40 help='disable verbose logging',
41 default=False)
Mads Ager418d1ca2017-05-22 09:35:49 +020042 (options, args) = parser.parse_args()
43 if len(args) != 1:
44 parser.error('Expected <apk> argument, got: ' + ' '.join(args))
45 apk = args[0]
Mads Ager418d1ca2017-05-22 09:35:49 +020046 return (options, apk)
47
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020048def repack(apk, processed_out, resources, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +020049 processed_apk = os.path.join(temp, 'processed.apk')
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010050 shutil.copyfile(apk, processed_apk)
Mads Ager418d1ca2017-05-22 09:35:49 +020051 if not processed_out:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010052 utils.Print('Using original APK as is', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +020053 return processed_apk
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010054 utils.Print(
55 'Repacking APK with dex files from {}'.format(processed_apk), quiet=quiet)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010056
57 # Delete original dex files in APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010058 with utils.ChangedWorkingDirectory(temp, quiet=quiet):
Mads Ager418d1ca2017-05-22 09:35:49 +020059 cmd = ['zip', '-d', 'processed.apk', '*.dex']
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020060 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010061
62 # Unzip the jar or zip file into `temp`.
Mads Ager418d1ca2017-05-22 09:35:49 +020063 if processed_out.endswith('.zip') or processed_out.endswith('.jar'):
64 cmd = ['unzip', processed_out, '-d', temp]
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010065 if quiet:
66 cmd.insert(1, '-q')
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020067 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020068 processed_out = temp
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010069
70 # Insert the new dex and resource files from `processed_out` into the APK.
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010071 with utils.ChangedWorkingDirectory(processed_out, quiet=quiet):
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010072 dex_files = glob.glob('*.dex')
73 resource_files = glob.glob(resources) if resources else []
74 cmd = ['zip', '-u', '-9', processed_apk] + dex_files + resource_files
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020075 utils.RunCmd(cmd, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020076 return processed_apk
77
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020078def sign(unsigned_apk, keystore, temp, quiet, logging):
Mads Ager418d1ca2017-05-22 09:35:49 +020079 signed_apk = os.path.join(temp, 'unaligned.apk')
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +020080 return apk_utils.sign_with_apksigner(
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020081 unsigned_apk, signed_apk, keystore, quiet=quiet, logging=logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020082
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020083def align(signed_apk, temp, quiet, logging):
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +010084 utils.Print('Aligning', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +020085 aligned_apk = os.path.join(temp, 'aligned.apk')
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +020086 return apk_utils.align(signed_apk, aligned_apk)
Mads Ager418d1ca2017-05-22 09:35:49 +020087
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +010088def masseur(
Christoffer Quist Adamsen2dcea102019-02-20 11:31:38 +010089 apk, dex=None, resources=None, out=None, adb_options=None, keystore=None,
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020090 install=False, quiet=False, logging=True):
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +010091 if not out:
92 out = os.path.basename(apk)
93 if not keystore:
Christoffer Quist Adamsen3f7e4282022-04-21 13:12:31 +020094 keystore = apk_utils.default_keystore()
Mads Ager418d1ca2017-05-22 09:35:49 +020095 with utils.TempDir() as temp:
96 processed_apk = None
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +010097 if dex:
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +020098 processed_apk = repack(apk, dex, resources, temp, quiet, logging)
Mads Ager418d1ca2017-05-22 09:35:49 +020099 else:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100100 utils.Print(
101 'Signing original APK without modifying dex files', quiet=quiet)
Mads Ager418d1ca2017-05-22 09:35:49 +0200102 processed_apk = os.path.join(temp, 'processed.apk')
103 shutil.copyfile(apk, processed_apk)
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200104 signed_apk = sign(
105 processed_apk, keystore, temp, quiet=quiet, logging=logging)
106 aligned_apk = align(signed_apk, temp, quiet=quiet, logging=logging)
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100107 utils.Print('Writing result to {}'.format(out), quiet=quiet)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100108 shutil.copyfile(aligned_apk, out)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100109 if install:
Christoffer Quist Adamsen1de3dde2019-01-24 13:17:46 +0100110 adb_cmd = ['adb']
111 if adb_options:
112 adb_cmd.extend(
113 [option for option in adb_options.split(' ') if option])
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100114 adb_cmd.extend(['install', '-t', '-r', '-d', out]);
Christoffer Quist Adamsen41cbdca2019-04-12 08:52:03 +0200115 utils.RunCmd(adb_cmd, quiet=quiet, logging=logging)
Christoffer Quist Adamsen4038bbe2019-01-15 14:31:46 +0100116
117def main():
118 (options, apk) = parse_options()
119 masseur(apk, **vars(options))
Mads Ager418d1ca2017-05-22 09:35:49 +0200120 return 0
121
122if __name__ == '__main__':
123 sys.exit(main())