diff --git a/app/buildSrc/src/main/java/AddCommentTask.kt b/app/buildSrc/src/main/java/AddCommentTask.kt new file mode 100644 index 000000000..c5af1ab4b --- /dev/null +++ b/app/buildSrc/src/main/java/AddCommentTask.kt @@ -0,0 +1,77 @@ +import com.android.build.api.artifact.ArtifactTransformationRequest +import com.android.build.api.dsl.ApkSigningConfig +import com.android.builder.internal.packaging.IncrementalPackager +import com.android.tools.build.apkzlib.sign.SigningExtension +import com.android.tools.build.apkzlib.sign.SigningOptions +import com.android.tools.build.apkzlib.zfile.ZFiles +import com.android.tools.build.apkzlib.zip.ZFileOptions +import org.gradle.api.DefaultTask +import org.gradle.api.file.DirectoryProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.InputFiles +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.OutputDirectory +import org.gradle.api.tasks.TaskAction +import java.io.File +import java.security.KeyStore +import java.security.cert.X509Certificate +import java.util.jar.JarFile + +abstract class AddCommentTask: DefaultTask() { + @get:Input + abstract val comment: Property + + @get:Input + abstract val signingConfig: Property + + @get:InputFiles + abstract val apkFolder: DirectoryProperty + + @get:OutputDirectory + abstract val outFolder: DirectoryProperty + + @get:Internal + abstract val transformationRequest: Property> + + @TaskAction + fun taskAction() = transformationRequest.get().submit(this) { artifact -> + val inFile = File(artifact.outputFile) + val outFile = outFolder.file(inFile.name).get().asFile + + val privateKey = signingConfig.get().getPrivateKey() + val signingOptions = SigningOptions.builder() + .setMinSdkVersion(0) + .setV1SigningEnabled(true) + .setV2SigningEnabled(true) + .setKey(privateKey.privateKey) + .setCertificates(privateKey.certificate as X509Certificate) + .setValidation(SigningOptions.Validation.ASSUME_INVALID) + .build() + val options = ZFileOptions().apply { + noTimestamps = true + autoSortFiles = true + } + outFile.parentFile?.mkdirs() + inFile.copyTo(outFile, overwrite = true) + ZFiles.apk(outFile, options).use { + SigningExtension(signingOptions).register(it) + it.eocdComment = comment.get().toByteArray() + it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete() + it.get(IncrementalPackager.VERSION_CONTROL_INFO_ENTRY_PATH)?.delete() + it.get(JarFile.MANIFEST_NAME)?.delete() + } + + outFile + } + + private fun ApkSigningConfig.getPrivateKey(): KeyStore.PrivateKeyEntry { + val keyStore = KeyStore.getInstance(storeType ?: KeyStore.getDefaultType()) + storeFile!!.inputStream().use { + keyStore.load(it, storePassword!!.toCharArray()) + } + val keyPwdArray = keyPassword!!.toCharArray() + val entry = keyStore.getEntry(keyAlias!!, KeyStore.PasswordProtection(keyPwdArray)) + return entry as KeyStore.PrivateKeyEntry + } +} \ No newline at end of file diff --git a/app/buildSrc/src/main/java/Plugin.kt b/app/buildSrc/src/main/java/Plugin.kt index 7e856358b..a1d99255e 100644 --- a/app/buildSrc/src/main/java/Plugin.kt +++ b/app/buildSrc/src/main/java/Plugin.kt @@ -5,6 +5,12 @@ import org.gradle.api.Project import org.gradle.kotlin.dsl.provideDelegate import java.io.File import java.util.Properties +import java.util.Random + +// 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 +lateinit var RANDOM: Random private val props = Properties() private var commitHash = "" diff --git a/app/buildSrc/src/main/java/Setup.kt b/app/buildSrc/src/main/java/Setup.kt index 1ba59d405..c7bb190cd 100644 --- a/app/buildSrc/src/main/java/Setup.kt +++ b/app/buildSrc/src/main/java/Setup.kt @@ -1,33 +1,18 @@ -import com.android.build.api.artifact.ArtifactTransformationRequest import com.android.build.api.artifact.SingleArtifact -import com.android.build.api.dsl.ApkSigningConfig import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS import com.android.build.api.instrumentation.InstrumentationScope import com.android.build.api.variant.ApplicationAndroidComponentsExtension import com.android.build.gradle.BaseExtension import com.android.build.gradle.LibraryExtension import com.android.build.gradle.internal.dsl.BaseAppModuleExtension -import com.android.builder.internal.packaging.IncrementalPackager -import com.android.tools.build.apkzlib.sign.SigningExtension -import com.android.tools.build.apkzlib.sign.SigningOptions -import com.android.tools.build.apkzlib.zfile.ZFiles -import com.android.tools.build.apkzlib.zip.ZFileOptions import org.apache.tools.ant.filters.FixCrLfFilter import org.gradle.api.Action -import org.gradle.api.DefaultTask import org.gradle.api.JavaVersion import org.gradle.api.Project -import org.gradle.api.file.DirectoryProperty -import org.gradle.api.provider.Property import org.gradle.api.tasks.Copy import org.gradle.api.tasks.Delete -import org.gradle.api.tasks.Input -import org.gradle.api.tasks.InputFiles -import org.gradle.api.tasks.Internal -import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.StopExecutionException import org.gradle.api.tasks.Sync -import org.gradle.api.tasks.TaskAction import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.exclude import org.gradle.kotlin.dsl.filter @@ -36,18 +21,14 @@ import org.gradle.kotlin.dsl.getValue import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.provideDelegate import org.gradle.kotlin.dsl.register -import org.gradle.kotlin.dsl.registering import org.gradle.kotlin.dsl.withType import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import java.io.ByteArrayOutputStream import java.io.File import java.net.URI -import java.security.KeyStore import java.security.MessageDigest -import java.security.cert.X509Certificate import java.util.HexFormat -import java.util.jar.JarFile import java.util.zip.Deflater import java.util.zip.DeflaterOutputStream import java.util.zip.ZipEntry @@ -60,13 +41,13 @@ private fun Project.androidBase(configure: Action) = private fun Project.android(configure: Action) = extensions.configure("android", configure) -private val Project.androidApp: BaseAppModuleExtension +internal val Project.androidApp: BaseAppModuleExtension get() = extensions["android"] as BaseAppModuleExtension private val Project.androidLib: LibraryExtension get() = extensions["android"] as LibraryExtension -private val Project.androidComponents +internal val Project.androidComponents get() = extensions.getByType(ApplicationAndroidComponentsExtension::class.java) fun Project.setupCommon() { @@ -145,46 +126,42 @@ const val BUSYBOX_ZIP_CHECKSUM = fun Project.setupCoreLib() { setupCommon() - val abiList = Config.abiList + androidLib.libraryVariants.all { + val variant = name + val variantCapped = name.replaceFirstChar { it.uppercase() } + val abiList = Config.abiList - val syncLibs by tasks.registering(Sync::class) { - into("src/main/jniLibs") - for (abi in abiList) { - into(abi) { - from(rootFile("native/out/$abi")) { - include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so") - rename { if (it.endsWith(".so")) it else "lib$it.so" } + val syncLibs = tasks.register("sync${variantCapped}JniLibs", Sync::class) { + into("src/$variant/jniLibs") + for (abi in abiList) { + into(abi) { + from(rootFile("native/out/$abi")) { + include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so") + rename { if (it.endsWith(".so")) it else "lib$it.so" } + } } } + from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM))) + include(abiList.map { "$it/libbusybox.so" }) + onlyIf { + if (inputs.sourceFiles.files.size != abiList.size * 6) + throw StopExecutionException("Please build binaries first! (./build.py binary)") + true + } } - onlyIf { - if (inputs.sourceFiles.files.size != abiList.size * 5) - throw StopExecutionException("Please build binaries first! (./build.py binary)") - true + + tasks.getByPath("merge${variantCapped}JniLibFolders").dependsOn(syncLibs) + + val syncResources = tasks.register("sync${variantCapped}Resources", Sync::class) { + into("src/$variant/resources/META-INF/com/google/android") + from(rootFile("scripts/update_binary.sh")) { + rename { "update-binary" } + } + from(rootFile("scripts/flash_script.sh")) { + rename { "updater-script" } + } } - } - val downloadBusybox by tasks.registering(Copy::class) { - dependsOn(syncLibs) - from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM))) - include(abiList.map { "$it/libbusybox.so" }) - into("src/main/jniLibs") - } - - val syncResources by tasks.registering(Sync::class) { - into("src/main/resources/META-INF/com/google/android") - from(rootFile("scripts/update_binary.sh")) { - rename { "update-binary" } - } - from(rootFile("scripts/flash_script.sh")) { - rename { "updater-script" } - } - } - - androidLib.libraryVariants.all { - val variantCapped = name.replaceFirstChar { it.uppercase() } - - tasks.getByPath("merge${variantCapped}JniLibFolders").dependsOn(downloadBusybox) processJavaResourcesProvider.configure { dependsOn(syncResources) } val stubTask = tasks.getByPath(":stub:comment$variantCapped") @@ -196,7 +173,7 @@ fun Project.setupCoreLib() { dependsOn(stubTask) inputs.property("version", Config.version) inputs.property("versionCode", Config.versionCode) - into("src/${this@all.name}/assets") + into("src/$variant/assets") from(rootFile("scripts")) { include("util_functions.sh", "boot_patch.sh", "addon.d.sh", "app_functions.sh", "uninstaller.sh", "module_installer.sh") @@ -229,64 +206,6 @@ fun Project.setupCoreLib() { } } -private fun ApkSigningConfig.getPrivateKey(): KeyStore.PrivateKeyEntry { - val keyStore = KeyStore.getInstance(storeType ?: KeyStore.getDefaultType()) - storeFile!!.inputStream().use { - keyStore.load(it, storePassword!!.toCharArray()) - } - val keyPwdArray = keyPassword!!.toCharArray() - val entry = keyStore.getEntry(keyAlias!!, KeyStore.PasswordProtection(keyPwdArray)) - return entry as KeyStore.PrivateKeyEntry -} - -abstract class AddCommentTask: DefaultTask() { - @get:Input - abstract val comment: Property - - @get:Input - abstract val signingConfig: Property - - @get:InputFiles - abstract val apkFolder: DirectoryProperty - - @get:OutputDirectory - abstract val outFolder: DirectoryProperty - - @get:Internal - abstract val transformationRequest: Property> - - @TaskAction - fun taskAction() = transformationRequest.get().submit(this) { artifact -> - val inFile = File(artifact.outputFile) - val outFile = outFolder.file(inFile.name).get().asFile - - val privateKey = signingConfig.get().getPrivateKey() - val signingOptions = SigningOptions.builder() - .setMinSdkVersion(0) - .setV1SigningEnabled(true) - .setV2SigningEnabled(true) - .setKey(privateKey.privateKey) - .setCertificates(privateKey.certificate as X509Certificate) - .setValidation(SigningOptions.Validation.ASSUME_INVALID) - .build() - val options = ZFileOptions().apply { - noTimestamps = true - autoSortFiles = true - } - outFile.parentFile?.mkdirs() - inFile.copyTo(outFile, overwrite = true) - ZFiles.apk(outFile, options).use { - SigningExtension(signingOptions).register(it) - it.eocdComment = comment.get().toByteArray() - it.get(IncrementalPackager.APP_METADATA_ENTRY_PATH)?.delete() - it.get(IncrementalPackager.VERSION_CONTROL_INFO_ENTRY_PATH)?.delete() - it.get(JarFile.MANIFEST_NAME)?.delete() - } - - outFile - } -} - fun Project.setupAppCommon() { setupCommon() @@ -382,89 +301,6 @@ fun Project.setupMainApk() { } } -fun Project.setupStubApk() { - setupAppCommon() - - androidComponents.onVariants { variant -> - val variantName = variant.name - val variantCapped = variantName.replaceFirstChar { it.uppercase() } - val manifestUpdater = - project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) { - dependsOn("generate${variantCapped}ObfuscatedClass") - applicationId = variant.applicationId - appClassDir.set(layout.buildDirectory.dir("generated/source/app/$variantName")) - factoryClassDir.set(layout.buildDirectory.dir("generated/source/factory/$variantName")) - } - variant.artifacts.use(manifestUpdater) - .wiredWithFiles( - ManifestUpdater::mergedManifest, - ManifestUpdater::outputManifest) - .toTransform(SingleArtifact.MERGED_MANIFEST) - } - - androidApp.applicationVariants.all { - val variantCapped = name.replaceFirstChar { it.uppercase() } - val variantLowered = name.lowercase() - val outFactoryClassDir = layout.buildDirectory.file("generated/source/factory/${variantLowered}").get().asFile - val outAppClassDir = layout.buildDirectory.file("generated/source/app/${variantLowered}").get().asFile - val outResDir = layout.buildDirectory.dir("generated/source/res/${variantLowered}").get().asFile - val aapt = File(androidApp.sdkDirectory, "build-tools/${androidApp.buildToolsVersion}/aapt2") - val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" + - "${variantLowered}/process${variantCapped}Resources/linked-resources-binary-format-${variantLowered}.ap_").get().asFile - - val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedClass") { - inputs.property("seed", RAND_SEED) - outputs.dirs(outFactoryClassDir, outAppClassDir) - doLast { - outFactoryClassDir.mkdirs() - outAppClassDir.mkdirs() - genStubClasses(outFactoryClassDir, outAppClassDir) - } - } - registerJavaGeneratingTask(genManifestTask, outFactoryClassDir, outAppClassDir) - - val processResourcesTask = tasks.named("process${variantCapped}Resources") { - outputs.dir(outResDir) - doLast { - val apkTmp = File("${apk}.tmp") - exec { - commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk) - } - - val bos = ByteArrayOutputStream() - ZipFile(apkTmp).use { src -> - ZipOutputStream(apk.outputStream()).use { - it.setLevel(Deflater.BEST_COMPRESSION) - it.putNextEntry(ZipEntry("AndroidManifest.xml")) - src.getInputStream(src.getEntry("AndroidManifest.xml")).transferTo(it) - it.closeEntry() - } - DeflaterOutputStream(bos, Deflater(Deflater.BEST_COMPRESSION)).use { - src.getInputStream(src.getEntry("resources.arsc")).transferTo(it) - } - } - apkTmp.delete() - genEncryptedResources(bos.toByteArray(), outResDir) - } - } - - registerJavaGeneratingTask(processResourcesTask, outResDir) - } - // Override optimizeReleaseResources task - val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" + - "release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile - val optRes = layout.buildDirectory.file("intermediates/optimized_processed_res/" + - "release/optimizeReleaseResources/resources-release-optimize.ap_").get().asFile - afterEvaluate { - tasks.named("optimizeReleaseResources") { - doLast { apk.copyTo(optRes, true) } - } - } - tasks.named("clean") { - delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml")) - } -} - const val LSPOSED_DOWNLOAD_URL = "https://github.com/LSPosed/LSPosed/releases/download/v1.9.2/LSPosed-v1.9.2-7024-zygisk-release.zip" const val LSPOSED_CHECKSUM = diff --git a/app/buildSrc/src/main/java/Codegen.kt b/app/buildSrc/src/main/java/Stub.kt similarity index 64% rename from app/buildSrc/src/main/java/Codegen.kt rename to app/buildSrc/src/main/java/Stub.kt index a1e74d7e0..8576eae5b 100644 --- a/app/buildSrc/src/main/java/Codegen.kt +++ b/app/buildSrc/src/main/java/Stub.kt @@ -1,8 +1,11 @@ +import com.android.build.api.artifact.SingleArtifact import org.gradle.api.DefaultTask +import org.gradle.api.Project import org.gradle.api.file.DirectoryProperty import org.gradle.api.file.RegularFileProperty import org.gradle.api.provider.Property import org.gradle.api.tasks.CacheableTask +import org.gradle.api.tasks.Delete import org.gradle.api.tasks.Input import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFiles @@ -10,22 +13,25 @@ import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.TaskAction +import org.gradle.kotlin.dsl.assign +import org.gradle.kotlin.dsl.named import java.io.ByteArrayInputStream import java.io.ByteArrayOutputStream import java.io.File import java.io.PrintStream import java.security.SecureRandom import java.util.Random +import java.util.zip.Deflater +import java.util.zip.DeflaterOutputStream +import java.util.zip.ZipEntry +import java.util.zip.ZipFile +import java.util.zip.ZipOutputStream 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() @@ -72,7 +78,7 @@ private fun PrintStream.byteField(name: String, bytes: ByteArray) { } @CacheableTask -abstract class ManifestUpdater: DefaultTask() { +private abstract class ManifestUpdater: DefaultTask() { @get:Input abstract val applicationId: Property @@ -182,9 +188,7 @@ abstract class ManifestUpdater: DefaultTask() { } -fun genStubClasses(factoryOutDir: File, appOutDir: File) { - fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level)) - +private fun genStubClasses(factoryOutDir: File, appOutDir: File) { val classNameGenerator = sequence { fun notJavaKeyword(name: String) = when (name) { "do", "if", "for", "int", "new", "try" -> false @@ -228,7 +232,7 @@ fun genStubClasses(factoryOutDir: File, appOutDir: File) { genClass("StubApplication", appOutDir) } -fun genEncryptedResources(res: ByteArray, outDir: File) { +private fun genEncryptedResources(res: ByteArray, outDir: File) { val mainPkgDir = File(outDir, "com/topjohnwu/magisk") mainPkgDir.mkdirs() @@ -259,3 +263,86 @@ fun genEncryptedResources(res: ByteArray, outDir: File) { it.println("}") } } + +fun Project.setupStubApk() { + setupAppCommon() + + androidComponents.onVariants { variant -> + val variantName = variant.name + val variantCapped = variantName.replaceFirstChar { it.uppercase() } + val manifestUpdater = + project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) { + dependsOn("generate${variantCapped}ObfuscatedClass") + applicationId = variant.applicationId + appClassDir.set(layout.buildDirectory.dir("generated/source/app/$variantName")) + factoryClassDir.set(layout.buildDirectory.dir("generated/source/factory/$variantName")) + } + variant.artifacts.use(manifestUpdater) + .wiredWithFiles( + ManifestUpdater::mergedManifest, + ManifestUpdater::outputManifest) + .toTransform(SingleArtifact.MERGED_MANIFEST) + } + + androidApp.applicationVariants.all { + val variantCapped = name.replaceFirstChar { it.uppercase() } + val variantLowered = name.lowercase() + val outFactoryClassDir = layout.buildDirectory.file("generated/source/factory/${variantLowered}").get().asFile + val outAppClassDir = layout.buildDirectory.file("generated/source/app/${variantLowered}").get().asFile + val outResDir = layout.buildDirectory.dir("generated/source/res/${variantLowered}").get().asFile + val aapt = File(androidApp.sdkDirectory, "build-tools/${androidApp.buildToolsVersion}/aapt2") + val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" + + "${variantLowered}/process${variantCapped}Resources/linked-resources-binary-format-${variantLowered}.ap_").get().asFile + + val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedClass") { + inputs.property("seed", RAND_SEED) + outputs.dirs(outFactoryClassDir, outAppClassDir) + doLast { + outFactoryClassDir.mkdirs() + outAppClassDir.mkdirs() + genStubClasses(outFactoryClassDir, outAppClassDir) + } + } + registerJavaGeneratingTask(genManifestTask, outFactoryClassDir, outAppClassDir) + + val processResourcesTask = tasks.named("process${variantCapped}Resources") { + outputs.dir(outResDir) + doLast { + val apkTmp = File("${apk}.tmp") + exec { + commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk) + } + + val bos = ByteArrayOutputStream() + ZipFile(apkTmp).use { src -> + ZipOutputStream(apk.outputStream()).use { + it.setLevel(Deflater.BEST_COMPRESSION) + it.putNextEntry(ZipEntry("AndroidManifest.xml")) + src.getInputStream(src.getEntry("AndroidManifest.xml")).transferTo(it) + it.closeEntry() + } + DeflaterOutputStream(bos, Deflater(Deflater.BEST_COMPRESSION)).use { + src.getInputStream(src.getEntry("resources.arsc")).transferTo(it) + } + } + apkTmp.delete() + genEncryptedResources(bos.toByteArray(), outResDir) + } + } + + registerJavaGeneratingTask(processResourcesTask, outResDir) + } + // Override optimizeReleaseResources task + val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" + + "release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile + val optRes = layout.buildDirectory.file("intermediates/optimized_processed_res/" + + "release/optimizeReleaseResources/resources-release-optimize.ap_").get().asFile + afterEvaluate { + tasks.named("optimizeReleaseResources") { + doLast { apk.copyTo(optRes, true) } + } + } + tasks.named("clean") { + delete.addAll(listOf("src/debug/AndroidManifest.xml", "src/release/AndroidManifest.xml")) + } +} diff --git a/app/core/.gitignore b/app/core/.gitignore index 6caebc6aa..e2350c9f8 100644 --- a/app/core/.gitignore +++ b/app/core/.gitignore @@ -1,4 +1,3 @@ /build -src/*/assets -src/*/jniLibs -src/*/resources +src/debug +src/release