import org.gradle.api.DefaultTask import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.* import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream import java.security.SecureRandom import java.util.* import javax.crypto.Cipher import javax.crypto.CipherOutputStream import javax.crypto.spec.IvParameterSpec import javax.crypto.spec.SecretKeySpec import kotlin.random.asKotlinRandom // Set non-zero value here to fix the random seed for reproducible builds // CI builds are always reproducible val RAND_SEED = if (System.getenv("CI") != null) 42 else 0 private lateinit var RANDOM: Random private val kRANDOM get() = RANDOM.asKotlinRandom() private val c1 = mutableListOf() private val c2 = mutableListOf() private val c3 = mutableListOf() fun initRandom(dict: File) { RANDOM = if (RAND_SEED != 0) Random(RAND_SEED.toLong()) else SecureRandom() c1.clear() c2.clear() c3.clear() for (a in chain('a'..'z', 'A'..'Z')) { if (a != 'a' && a != 'A') { c1.add("$a") } for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) { c2.add("$a$b") for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) { c3.add("$a$b$c") } } } c1.shuffle(RANDOM) c2.shuffle(RANDOM) c3.shuffle(RANDOM) PrintStream(dict).use { for (c in chain(c1, c2, c3)) { it.println(c) } } } private fun chain(vararg iters: Iterable) = sequence { iters.forEach { it.forEach { v -> yield(v) } } } private fun PrintStream.byteField(name: String, bytes: ByteArray) { println("public static byte[] $name() {") print("byte[] buf = {") print(bytes.joinToString(",") { it.toString() }) println("};") println("return buf;") println("}") } fun genKeyData(keysDir: File, outSrc: File) { outSrc.parentFile.mkdirs() PrintStream(outSrc).use { it.println("package com.topjohnwu.magisk.signing;") it.println("public final class KeyData {") it.byteField("verityCert", File(keysDir, "verity.x509.pem").readBytes()) it.byteField("verityKey", File(keysDir, "verity.pk8").readBytes()) it.println("}") } } @CacheableTask abstract class ManifestUpdater: DefaultTask() { @get:Input abstract val applicationId: Property @get:InputFile @get:PathSensitive(PathSensitivity.RELATIVE) abstract val mergedManifest: RegularFileProperty @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val factoryClassDir: DirectoryProperty @get:InputFiles @get:PathSensitive(PathSensitivity.RELATIVE) abstract val appClassDir: DirectoryProperty @get:OutputFile abstract val outputManifest: RegularFileProperty @TaskAction fun taskAction() { fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level)) val cmpList = mutableListOf() cmpList.add(""" |""".ind(2) ) cmpList.add(""" | | | | | | | | | | | | |""".ind(2) ) cmpList.add(""" | | | | | |""".ind(2) ) cmpList.add(""" | | | | | |""".ind(2) ) cmpList.add(""" |""".ind(2) ) cmpList.add(""" |""".ind(2) ) // Shuffle the order of the components cmpList.shuffle(RANDOM) val (factoryPkg, factoryClass) = factoryClassDir.asFileTree.first().let { it.parentFile.name to it.name.removeSuffix(".java") } val (appPkg, appClass) = appClassDir.asFileTree.first().let { it.parentFile.name to it.name.removeSuffix(".java") } val components = cmpList.joinToString("\n\n") .replace("\${applicationId}", applicationId.get()) val manifest = mergedManifest.asFile.get().readText().replace(Regex(".*\\ false else -> true } fun List.process() = asSequence() .filter(::notJavaKeyword) // Distinct by lower case to support case insensitive file systems .distinctBy { it.lowercase() } val names = mutableListOf() names.addAll(c1) names.addAll(c2.process().take(30)) names.addAll(c3.process().take(30)) names.shuffle(RANDOM) while (true) { val cls = StringBuilder() cls.append(names.random(kRANDOM)) cls.append('.') cls.append(names.random(kRANDOM)) // Old Android does not support capitalized package names // Check Android 7.0.0 PackageParser#buildClassName yield(cls.toString().replaceFirstChar { it.lowercase() }) } }.distinct().iterator() fun genClass(type: String, outDir: File) { val clzName = classNameGenerator.next() val (pkg, name) = clzName.split('.') val pkgDir = File(outDir, pkg) pkgDir.mkdirs() PrintStream(File(pkgDir, "$name.java")).use { it.println("package $pkg;") it.println("public class $name extends com.topjohnwu.magisk.$type {}") } } genClass("DelegateComponentFactory", factoryOutDir) genClass("DelegateApplication", appOutDir) } fun genEncryptedResources(res: ByteArray, outDir: File) { val mainPkgDir = File(outDir, "com/topjohnwu/magisk") mainPkgDir.mkdirs() // Generate iv and key val iv = ByteArray(16) val key = ByteArray(32) RANDOM.nextBytes(iv) RANDOM.nextBytes(key) val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding") cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv)) val bos = ByteArrayOutputStream() ByteArrayInputStream(res).use { CipherOutputStream(bos, cipher).use { os -> it.transferTo(os) } } PrintStream(File(mainPkgDir, "Bytes.java")).use { it.println("package com.topjohnwu.magisk;") it.println("public final class Bytes {") it.byteField("key", key) it.byteField("iv", iv) it.byteField("res", bos.toByteArray()) it.println("}") } }