Use AGP to compile resources

This commit is contained in:
南宫雪珊 2021-12-14 21:30:15 +08:00 committed by GitHub
parent baa19f0ccf
commit df191cd2b5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
49 changed files with 89 additions and 130 deletions

View File

@ -39,11 +39,6 @@ android {
dataBinding = true
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
packagingOptions {
resources {
excludes += "/META-INF/*"

View File

@ -1,11 +1,9 @@
import java.io.ByteArrayOutputStream
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.PrintStream
import java.security.SecureRandom
import java.util.*
import java.util.zip.GZIPOutputStream
import javax.crypto.Cipher
import javax.crypto.CipherOutputStream
import javax.crypto.spec.IvParameterSpec
@ -54,7 +52,7 @@ private fun <T> chain(vararg iters: Iterable<T>) = sequence {
private fun PrintStream.byteField(name: String, bytes: ByteArray) {
println("public static byte[] $name() {")
print("byte[] buf = {")
print(bytes.joinToString(",") { "(byte)(${it.toInt() and 0xff})" })
print(bytes.joinToString(",") { it.toString() })
println("};")
println("return buf;")
println("}")
@ -192,10 +190,8 @@ fun genStubManifest(srcDir: File, outDir: File): String {
names.addAll(c3.subList(0, 10))
names.shuffle(RANDOM)
// Decapitalize as older Android versions do not allow capitalized package names
// Distinct by lower case to support case insensitive file systems
val pkgNames = names.map { it.decapitalize(Locale.ROOT) }
.distinctBy { it.toLowerCase(Locale.ROOT) }
val pkgNames = names.distinctBy { it.toLowerCase(Locale.ROOT) }
var idx = 0
fun genCmpName() = "${pkgNames[idx++]}.${names.random(kRANDOM)}"
@ -247,15 +243,8 @@ fun genStubManifest(srcDir: File, outDir: File): String {
return genXml
}
fun genEncryptedResources(res: File, outDir: File) {
fun genEncryptedResources(res: InputStream, outDir: File) {
val mainPkgDir = File(outDir, "com/topjohnwu/magisk")
// Rename R.java
val r = File(mainPkgDir, "R.java").let {
val txt = it.readText()
it.delete()
txt
}
File(mainPkgDir, "R2.java").writeText(r.replace("class R", "class R2"))
// Generate iv and key
val iv = ByteArray(16)
@ -267,9 +256,8 @@ fun genEncryptedResources(res: File, outDir: File) {
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
val bos = ByteArrayOutputStream()
FileInputStream(res).use {
// First compress, then encrypt
GZIPOutputStream(CipherOutputStream(bos, cipher)).use { os ->
res.use {
CipherOutputStream(bos, cipher).use { os ->
it.transferTo(os)
}
}

View File

@ -1,30 +1,30 @@
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.internal.dsl.BaseAppModuleExtension
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.tasks.StopExecutionException
import org.gradle.api.tasks.Sync
import org.gradle.internal.os.OperatingSystem
import org.gradle.kotlin.dsl.filter
import org.gradle.kotlin.dsl.named
import java.io.File
import java.io.OutputStream
import java.io.PrintStream
import java.nio.file.Paths
import java.io.*
import java.util.*
import java.util.zip.Deflater
import java.util.zip.ZipEntry
import java.util.zip.ZipFile
import java.util.zip.ZipOutputStream
private fun Project.android(configure: Action<BaseExtension>) =
private fun Project.androidBase(configure: Action<BaseExtension>) =
extensions.configure("android", configure)
private val Project.android: BaseAppModuleExtension get() =
extensions.getByName("android") as BaseAppModuleExtension
private fun Project.android(configure: Action<BaseAppModuleExtension>) =
extensions.configure("android", configure)
private val Project.android: BaseAppModuleExtension
get() = extensions.getByName("android") as BaseAppModuleExtension
fun Project.setupCommon() {
android {
androidBase {
compileSdkVersion(31)
buildToolsVersion = "31.0.0"
ndkPath = "${System.getenv("ANDROID_SDK_ROOT")}/ndk/magisk"
@ -69,9 +69,13 @@ private fun Project.setupAppCommon() {
}
}
lintOptions {
lint {
disable += "MissingTranslation"
}
dependenciesInfo {
includeInApk = false
}
}
}
@ -147,11 +151,9 @@ fun Project.setupApp() {
}
}
tasks.named<DefaultTask>("preBuild") {
dependsOn(syncResources)
}
android.applicationVariants.all {
preBuildProvider.get().dependsOn(syncResources)
val keysDir = rootProject.file("tools/keys")
val outSrcDir = File(buildDir, "generated/source/keydata/$name")
val outSrc = File(outSrcDir, "com/topjohnwu/magisk/signing/KeyData.java")
@ -170,91 +172,67 @@ fun Project.setupApp() {
fun Project.setupStub() {
setupAppCommon()
// Make sure we have a working manifest while building
val ensureManifest = tasks.register("ensureManifest") {
val manifest = file("src/main/AndroidManifest.xml")
if (!manifest.exists()) {
PrintStream(manifest).use {
it.println("<manifest package=\"com.topjohnwu.magisk\"/>")
}
}
}
tasks.named<DefaultTask>("preBuild") {
dependsOn(ensureManifest)
}
android.applicationVariants.all {
val manifest = file("src/main/AndroidManifest.xml")
val outSrcDir = File(buildDir, "generated/source/obfuscate/$name")
val variantCapped = name.capitalize(Locale.ROOT)
val variantLowered = name.toLowerCase(Locale.ROOT)
val manifest = file("src/${variantLowered}/AndroidManifest.xml")
val outSrcDir = File(buildDir, "generated/source/obfuscate/${variantLowered}")
val templateDir = file("template")
val resDir = file("res")
val aapt = File(android.sdkDirectory, "build-tools/${android.buildToolsVersion}/aapt2")
val apk = File(buildDir, "intermediates/processed_res/" +
"${variantLowered}/out/resources-${variantLowered}.ap_")
val apkTmp = File("${apk}.tmp")
val androidJar = Paths.get(android.sdkDirectory.path, "platforms",
android.compileSdkVersion, "android.jar")
val aaptCommand = if (OperatingSystem.current().isWindows) "aapt2.exe" else "aapt2"
val aapt = Paths.get(android.sdkDirectory.path,
"build-tools", android.buildToolsVersion, aaptCommand)
val dummy = object : OutputStream() {
override fun write(b: Int) {}
override fun write(bytes: ByteArray, off: Int, len: Int) {}
}
val genSrcTask = tasks.register("generate${name.capitalize(Locale.ROOT)}ObfuscatedSources") {
val genManifestTask = tasks.register("generate${variantCapped}ObfuscatedManifest") {
doLast {
val xml = genStubManifest(templateDir, outSrcDir)
manifest.parentFile.mkdirs()
PrintStream(manifest).use {
it.print(xml)
}
}
}
mergeResourcesProvider.get().dependsOn(genManifestTask)
val compileTmp = File.createTempFile("tmp", ".zip")
val linkTmp = File.createTempFile("tmp", ".zip")
val optTmp = File.createTempFile("tmp", ".zip")
val stubXml = File.createTempFile("tmp", ".xml")
try {
PrintStream(stubXml).use {
it.println("<manifest package=\"com.topjohnwu.magisk\"/>")
}
exec {
commandLine(aapt, "compile",
"-o", compileTmp,
"--dir", resDir)
standardOutput = dummy
errorOutput = dummy
}
exec {
commandLine(aapt, "link",
"-o", linkTmp,
"-I", androidJar,
"--min-sdk-version", android.defaultConfig.minSdk,
"--target-sdk-version", android.defaultConfig.targetSdk,
"--manifest", stubXml,
"--java", outSrcDir, compileTmp)
standardOutput = dummy
errorOutput = dummy
}
exec {
commandLine(aapt, "optimize",
"-o", optTmp,
"--collapse-resource-names", linkTmp)
standardOutput = dummy
errorOutput = dummy
}
genEncryptedResources(optTmp, outSrcDir)
} finally {
compileTmp.delete()
linkTmp.delete()
optTmp.delete()
stubXml.delete()
val genSrcTask = tasks.register("generate${variantCapped}ObfuscatedSources") {
dependsOn(":stub:process${variantCapped}Resources")
doLast {
exec {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
}
val buffer = ByteArrayOutputStream(apk.length().toInt())
val newApk = ZipOutputStream(FileOutputStream(apk))
ZipFile(apkTmp).use {
newApk.use { new ->
new.setLevel(Deflater.BEST_COMPRESSION)
new.putNextEntry(ZipEntry("AndroidManifest.xml"))
it.getInputStream(it.getEntry("AndroidManifest.xml")).transferTo(new)
new.closeEntry()
new.finish()
}
ZipOutputStream(buffer).use { arsc ->
arsc.setLevel(Deflater.BEST_COMPRESSION)
arsc.putNextEntry(ZipEntry("resources.arsc"))
it.getInputStream(it.getEntry("resources.arsc")).transferTo(arsc)
arsc.closeEntry()
arsc.finish()
}
}
apkTmp.delete()
genEncryptedResources(ByteArrayInputStream(buffer.toByteArray()), outSrcDir)
}
}
registerJavaGeneratingTask(genSrcTask, outSrcDir)
}
// Override optimizeReleaseResources task
tasks.whenTaskAdded {
val apk = File(buildDir, "intermediates/processed_res/" +
"release/out/resources-release.ap_")
val optRes = File(buildDir, "intermediates/optimized_processed_res/" +
"release/resources-release-optimize.ap_")
if (name == "optimizeReleaseResources") {
doLast { apk.copyTo(optRes, true) }
}
}
}

3
stub/.gitignore vendored
View File

@ -1,2 +1,3 @@
/build
/src/main/AndroidManifest.xml
/src/release/AndroidManifest.xml
/src/debug/AndroidManifest.xml

View File

@ -29,11 +29,6 @@ android {
proguardFiles("proguard-rules.pro")
}
}
dependenciesInfo {
includeInApk = false
includeInBundle = false
}
}
setupStub()

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest package="com.topjohnwu.magisk" />

View File

@ -3,10 +3,10 @@ package com.topjohnwu.magisk;
import static android.R.string.no;
import static android.R.string.ok;
import static android.R.string.yes;
import static com.topjohnwu.magisk.R2.string.dling;
import static com.topjohnwu.magisk.R2.string.no_internet_msg;
import static com.topjohnwu.magisk.R2.string.relaunch_app;
import static com.topjohnwu.magisk.R2.string.upgrade_msg;
import static com.topjohnwu.magisk.R.string.dling;
import static com.topjohnwu.magisk.R.string.no_internet_msg;
import static com.topjohnwu.magisk.R.string.relaunch_app;
import static com.topjohnwu.magisk.R.string.upgrade_msg;
import android.app.Activity;
import android.app.AlertDialog;
@ -28,9 +28,6 @@ import org.json.JSONException;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.zip.GZIPInputStream;
import javax.crypto.Cipher;
import javax.crypto.CipherInputStream;
@ -140,10 +137,13 @@ public class DownloadActivity extends Activity {
SecretKey key = new SecretKeySpec(Bytes.key(), "AES");
IvParameterSpec iv = new IvParameterSpec(Bytes.iv());
cipher.init(Cipher.DECRYPT_MODE, key, iv);
InputStream is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher);
try (InputStream gzip = new GZIPInputStream(is);
OutputStream out = new FileOutputStream(apk)) {
APKInstall.transfer(gzip, out);
var is = new CipherInputStream(new ByteArrayInputStream(Bytes.res()), cipher);
var out = new FileOutputStream(apk);
try (is; out) {
byte[] buf = new byte[4096];
for (int read; (read = is.read(buf)) >= 0;) {
out.write(buf, 0, read);
}
}
DynAPK.addAssetPath(getResources().getAssets(), apk.getPath());
} catch (Exception ignored) {