@@ -18,10 +18,9 @@ import java.io.PrintWriter
18
18
import java.io.StringWriter
19
19
import java.util.logging.Logger
20
20
21
-
22
21
@CommandLine.Command (
23
22
name = " patch" ,
24
- description = [" Patch an APK file." ]
23
+ description = [" Patch an APK file." ],
25
24
)
26
25
internal object PatchCommand : Runnable {
27
26
private val logger = Logger .getLogger(PatchCommand ::class .java.name)
@@ -37,25 +36,25 @@ internal object PatchCommand : Runnable {
37
36
38
37
@CommandLine.Option (
39
38
names = [" -i" , " --include" ],
40
- description = [" List of patches to include." ]
39
+ description = [" List of patches to include." ],
41
40
)
42
41
private var includedPatches = hashSetOf<String >()
43
42
44
43
@CommandLine.Option (
45
44
names = [" --ii" ],
46
- description = [" List of patches to include by their index in relation to the supplied patch bundles." ]
45
+ description = [" List of patches to include by their index in relation to the supplied patch bundles." ],
47
46
)
48
47
private var includedPatchesByIndex = arrayOf<Int >()
49
48
50
49
@CommandLine.Option (
51
50
names = [" -e" , " --exclude" ],
52
- description = [" List of patches to exclude." ]
51
+ description = [" List of patches to exclude." ],
53
52
)
54
53
private var excludedPatches = hashSetOf<String >()
55
54
56
55
@CommandLine.Option (
57
56
names = [" --ei" ],
58
- description = [" List of patches to exclude by their index in relation to the supplied patch bundles." ]
57
+ description = [" List of patches to exclude by their index in relation to the supplied patch bundles." ],
59
58
)
60
59
private var excludedPatchesByIndex = arrayOf<Int >()
61
60
@@ -68,14 +67,14 @@ internal object PatchCommand : Runnable {
68
67
@CommandLine.Option (
69
68
names = [" --exclusive" ],
70
69
description = [" Only include patches that are explicitly specified to be included." ],
71
- showDefaultValue = ALWAYS
70
+ showDefaultValue = ALWAYS ,
72
71
)
73
72
private var exclusive = false
74
73
75
74
@CommandLine.Option (
76
- names = [" -f" ," --force" ],
75
+ names = [" -f" , " --force" ],
77
76
description = [" Bypass compatibility checks for the supplied APK's version." ],
78
- showDefaultValue = ALWAYS
77
+ showDefaultValue = ALWAYS ,
79
78
)
80
79
private var force: Boolean = false
81
80
@@ -91,48 +90,52 @@ internal object PatchCommand : Runnable {
91
90
92
91
@CommandLine.Option (
93
92
names = [" -d" , " --device-serial" ],
94
- description = [" ADB device serial to install to." ],
93
+ description = [" ADB device serial to install to. If not supplied, the first connected device will be used." ],
94
+ fallbackValue = " " , // Empty string to indicate that the first connected device should be used.
95
+ arity = " 0..1" ,
95
96
)
96
97
private var deviceSerial: String? = null
97
98
98
99
@CommandLine.Option (
99
100
names = [" --mount" ],
100
101
description = [" Install by mounting the patched APK file." ],
101
- showDefaultValue = ALWAYS
102
+ showDefaultValue = ALWAYS ,
102
103
)
103
104
private var mount: Boolean = false
104
105
105
106
@CommandLine.Option (
106
107
names = [" --keystore" ],
107
- description = [" Path to the keystore to sign the patched APK file with. " +
108
- " Defaults to the same directory as the supplied APK file." ],
108
+ description = [
109
+ " Path to the keystore to sign the patched APK file with. " +
110
+ " Defaults to the same directory as the supplied APK file." ,
111
+ ],
109
112
)
110
113
private var keystoreFilePath: File ? = null
111
114
112
115
// key store password
113
116
@CommandLine.Option (
114
117
names = [" --keystore-password" ],
115
- description = [" The password of the keystore to sign the patched APK file with. Empty password by default." ]
118
+ description = [" The password of the keystore to sign the patched APK file with. Empty password by default." ],
116
119
)
117
120
private var keyStorePassword: String? = null // Empty password by default
118
121
119
122
@CommandLine.Option (
120
123
names = [" --alias" ],
121
124
description = [" The alias of the key from the keystore to sign the patched APK file with." ],
122
- showDefaultValue = ALWAYS
125
+ showDefaultValue = ALWAYS ,
123
126
)
124
127
private var alias = " ReVanced Key"
125
128
126
129
@CommandLine.Option (
127
130
names = [" --keystore-entry-password" ],
128
- description = [" The password of the entry from the keystore for the key to sign the patched APK file with." ]
131
+ description = [" The password of the entry from the keystore for the key to sign the patched APK file with." ],
129
132
)
130
133
private var password = " " // Empty password by default
131
134
132
135
@CommandLine.Option (
133
136
names = [" --signer" ],
134
137
description = [" The name of the signer to sign the patched APK file with." ],
135
- showDefaultValue = ALWAYS
138
+ showDefaultValue = ALWAYS ,
136
139
)
137
140
private var signer = " ReVanced"
138
141
@@ -147,33 +150,35 @@ internal object PatchCommand : Runnable {
147
150
@CommandLine.Option (
148
151
names = [" -p" , " --purge" ],
149
152
description = [" Purge the temporary resource cache directory after patching." ],
150
- showDefaultValue = ALWAYS
153
+ showDefaultValue = ALWAYS ,
151
154
)
152
155
private var purge: Boolean = false
153
156
154
157
@CommandLine.Option (
155
158
names = [" -w" , " --warn" ],
156
159
description = [" Warn if a patch can not be found in the supplied patch bundles." ],
157
- showDefaultValue = ALWAYS
160
+ showDefaultValue = ALWAYS ,
158
161
)
159
162
private var warn: Boolean = false
160
163
161
164
@CommandLine.Parameters (
162
165
description = [" APK file to be patched." ],
163
- arity = " 1..1"
166
+ arity = " 1..1" ,
164
167
)
165
168
@Suppress(" unused" )
166
169
private fun setApk (apk : File ) {
167
- if (! apk.exists()) throw CommandLine .ParameterException (
168
- spec.commandLine(),
169
- " APK file ${apk.name} does not exist"
170
- )
170
+ if (! apk.exists()) {
171
+ throw CommandLine .ParameterException (
172
+ spec.commandLine(),
173
+ " APK file ${apk.name} does not exist" ,
174
+ )
175
+ }
171
176
this .apk = apk
172
177
}
173
178
174
179
@CommandLine.Option (
175
180
names = [" -m" , " --merge" ],
176
- description = [" One or more DEX files or containers to merge into the APK." ]
181
+ description = [" One or more DEX files or containers to merge into the APK." ],
177
182
)
178
183
@Suppress(" unused" )
179
184
private fun setIntegrations (integrations : Array <File >) {
@@ -186,7 +191,7 @@ internal object PatchCommand : Runnable {
186
191
@CommandLine.Option (
187
192
names = [" -b" , " --patch-bundle" ],
188
193
description = [" One or more bundles of patches." ],
189
- required = true
194
+ required = true ,
190
195
)
191
196
@Suppress(" unused" )
192
197
private fun setPatchBundles (patchBundles : Array <File >) {
@@ -198,37 +203,37 @@ internal object PatchCommand : Runnable {
198
203
199
204
@CommandLine.Option (
200
205
names = [" --custom-aapt2-binary" ],
201
- description = [" Path to a custom AAPT binary to compile resources with." ]
206
+ description = [" Path to a custom AAPT binary to compile resources with." ],
202
207
)
203
208
@Suppress(" unused" )
204
209
private fun setAaptBinaryPath (aaptBinaryPath : File ) {
205
- if (! aaptBinaryPath.exists()) throw CommandLine .ParameterException (
206
- spec.commandLine(),
207
- " AAPT binary ${aaptBinaryPath.name} does not exist"
208
- )
210
+ if (! aaptBinaryPath.exists()) {
211
+ throw CommandLine .ParameterException (
212
+ spec.commandLine(),
213
+ " AAPT binary ${aaptBinaryPath.name} does not exist" ,
214
+ )
215
+ }
209
216
this .aaptBinaryPath = aaptBinaryPath
210
217
}
211
218
212
219
override fun run () {
213
220
// region Setup
214
221
215
222
val outputFilePath = outputFilePath ? : File (" " ).absoluteFile.resolve(
216
- " ${apk.nameWithoutExtension} -patched.${apk.extension} "
223
+ " ${apk.nameWithoutExtension} -patched.${apk.extension} " ,
217
224
)
218
225
219
226
val resourceCachePath = resourceCachePath ? : outputFilePath.parentFile.resolve(
220
- " ${outputFilePath.nameWithoutExtension} -resource-cache"
227
+ " ${outputFilePath.nameWithoutExtension} -resource-cache" ,
221
228
)
222
229
223
230
val optionsFile = optionsFile ? : outputFilePath.parentFile.resolve(
224
- " ${outputFilePath.nameWithoutExtension} -options.json"
231
+ " ${outputFilePath.nameWithoutExtension} -options.json" ,
225
232
)
226
233
227
234
val keystoreFilePath = keystoreFilePath ? : outputFilePath.parentFile
228
235
.resolve(" ${outputFilePath.nameWithoutExtension} .keystore" )
229
236
230
- val adbManager = deviceSerial?.let { serial -> AdbManager .getAdbManager(serial, mount) }
231
-
232
237
// endregion
233
238
234
239
// region Load patches
@@ -238,13 +243,15 @@ internal object PatchCommand : Runnable {
238
243
val patches = PatchBundleLoader .Jar (* patchBundles.toTypedArray())
239
244
240
245
// Warn if a patch can not be found in the supplied patch bundles.
241
- if (warn) patches.map { it.name }.toHashSet().let { availableNames ->
242
- (includedPatches + excludedPatches).filter { name ->
243
- ! availableNames.contains(name)
246
+ if (warn) {
247
+ patches.map { it.name }.toHashSet().let { availableNames ->
248
+ (includedPatches + excludedPatches).filter { name ->
249
+ ! availableNames.contains(name)
250
+ }
251
+ }.let { unknownPatches ->
252
+ if (unknownPatches.isEmpty()) return @let
253
+ logger.warning(" Unknown input of patches:\n ${unknownPatches.joinToString(" \n " )} " )
244
254
}
245
- }.let { unknownPatches ->
246
- if (unknownPatches.isEmpty()) return @let
247
- logger.warning(" Unknown input of patches:\n ${unknownPatches.joinToString(" \n " )} " )
248
255
}
249
256
250
257
// endregion
@@ -255,14 +262,17 @@ internal object PatchCommand : Runnable {
255
262
resourceCachePath,
256
263
aaptBinaryPath?.path,
257
264
resourceCachePath.absolutePath,
258
- true
259
- )
265
+ true ,
266
+ ),
260
267
).use { patcher ->
261
268
val filteredPatches = patcher.filterPatchSelection(patches).also { patches ->
262
269
logger.info(" Setting patch options" )
263
270
264
- if (optionsFile.exists()) patches.setOptions(optionsFile)
265
- else Options .serialize(patches, prettyPrint = true ).let (optionsFile::writeText)
271
+ if (optionsFile.exists()) {
272
+ patches.setOptions(optionsFile)
273
+ } else {
274
+ Options .serialize(patches, prettyPrint = true ).let (optionsFile::writeText)
275
+ }
266
276
}
267
277
268
278
// region Patch
@@ -292,24 +302,29 @@ internal object PatchCommand : Runnable {
292
302
ApkUtils .copyAligned(apk, this , patcherResult)
293
303
}
294
304
295
- if (! mount) ApkUtils .sign(
296
- alignedFile,
297
- outputFilePath,
298
- ApkUtils .SigningOptions (
299
- keystoreFilePath,
300
- keyStorePassword,
301
- alias,
302
- password,
303
- signer
305
+ if (! mount) {
306
+ ApkUtils .sign(
307
+ alignedFile,
308
+ outputFilePath,
309
+ ApkUtils .SigningOptions (
310
+ keystoreFilePath,
311
+ keyStorePassword,
312
+ alias,
313
+ password,
314
+ signer,
315
+ ),
304
316
)
305
- )
306
- else alignedFile.renameTo(outputFilePath)
317
+ } else {
318
+ alignedFile.renameTo(outputFilePath)
319
+ }
307
320
308
321
// endregion
309
322
310
323
// region Install
311
324
312
- adbManager?.install(AdbManager .Apk (outputFilePath, patcher.context.packageMetadata.packageName))
325
+ deviceSerial?.let { serial ->
326
+ AdbManager .getAdbManager(deviceSerial = serial.ifEmpty { null }, mount)
327
+ }?.install(AdbManager .Apk (outputFilePath, patcher.context.packageMetadata.packageName))
313
328
314
329
// endregion
315
330
}
@@ -320,7 +335,6 @@ internal object PatchCommand : Runnable {
320
335
}
321
336
}
322
337
323
-
324
338
/* *
325
339
* Filter the patches to be added to the patcher. The filter is based on the following:
326
340
*
@@ -344,17 +358,20 @@ internal object PatchCommand : Runnable {
344
358
it.any { version -> version == packageVersion }
345
359
} ? : true
346
360
347
- if (! matchesVersion) return @patch logger.warning(
348
- " $patchName is incompatible with version $packageVersion . "
349
- + " This patch is only compatible with version "
350
- + packages.joinToString(" ;" ) { pkg ->
351
- pkg.versions!! .joinToString(" , " )
352
- }
353
- )
361
+ if (! matchesVersion) {
362
+ return @patch logger.warning(
363
+ " $patchName is incompatible with version $packageVersion . " +
364
+ " This patch is only compatible with version " +
365
+ packages.joinToString(" ;" ) { pkg ->
366
+ pkg.versions!! .joinToString(" , " )
367
+ },
368
+ )
369
+ }
354
370
} ? : return @patch logger.fine(
355
- " $patchName is incompatible with $packageName . "
356
- + " This patch is only compatible with "
357
- + packages.joinToString(" , " ) { `package` -> `package`.name })
371
+ " $patchName is incompatible with $packageName . " +
372
+ " This patch is only compatible with " +
373
+ packages.joinToString(" , " ) { `package` -> `package`.name },
374
+ )
358
375
359
376
return @let
360
377
} ? : logger.fine(" $patchName has no constraint on packages." )
@@ -374,8 +391,11 @@ internal object PatchCommand : Runnable {
374
391
}
375
392
376
393
private fun purge (resourceCachePath : File ) {
377
- val result = if (resourceCachePath.deleteRecursively()) " Purged resource cache directory"
378
- else " Failed to purge resource cache directory"
394
+ val result = if (resourceCachePath.deleteRecursively()) {
395
+ " Purged resource cache directory"
396
+ } else {
397
+ " Failed to purge resource cache directory"
398
+ }
379
399
logger.info(result)
380
400
}
381
- }
401
+ }
0 commit comments