Compare commits

..

3 Commits

Author SHA1 Message Date
Pzqqt
0919db6b11 magiskboot: Avoid implicit type conversion
Fix #9607
2026-01-19 23:33:00 +08:00
topjohnwu
18c1347bd3 Update to AGP 9.0 2026-01-19 23:29:02 +08:00
topjohnwu
0bbc736051 Properly set abiList with config.prop for APK 2025-12-16 02:38:27 -08:00
20 changed files with 267 additions and 235 deletions

2
app/.gitignore vendored
View File

@@ -3,5 +3,5 @@
# Gradle # Gradle
.gradle .gradle
.kotlin .kotlin
build
/local.properties /local.properties
/build

1
app/apk/.gitignore vendored
View File

@@ -1 +0,0 @@
/build

View File

@@ -1,8 +1,7 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
kotlin("android")
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
kotlin("kapt") id("com.android.legacy-kapt")
id("androidx.navigation.safeargs.kotlin") id("androidx.navigation.safeargs.kotlin")
} }
@@ -26,6 +25,10 @@ android {
isCoreLibraryDesugaringEnabled = true isCoreLibraryDesugaringEnabled = true
} }
defaultConfig {
proguardFile("proguard-rules.pro")
}
buildTypes { buildTypes {
release { release {
isMinifyEnabled = true isMinifyEnabled = true

3
app/apk/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,3 @@
# Excessive obfuscation
-flattenpackagehierarchy
-allowaccessmodification

View File

@@ -1 +0,0 @@
/build

View File

@@ -1,5 +1,3 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
} }
@@ -21,6 +19,7 @@ gradlePlugin {
dependencies { dependencies {
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get())) implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
implementation(libs.android.gradle.plugin) implementation(libs.android.gradle.plugin)
implementation(libs.android.kapt.plugin)
implementation(libs.ksp.plugin) implementation(libs.ksp.plugin)
implementation(libs.navigation.safe.args.plugin) implementation(libs.navigation.safe.args.plugin)
implementation(libs.lsparanoid.plugin) implementation(libs.lsparanoid.plugin)

View File

@@ -46,11 +46,25 @@ class MagiskPlugin : Plugin<Project> {
private fun Project.applyPlugin() { private fun Project.applyPlugin() {
initRandom(rootProject.file("dict.txt")) initRandom(rootProject.file("dict.txt"))
props.clear() props.clear()
rootProject.file("gradle.properties").inputStream().use { props.load(it) }
// Get gradle properties relevant to Magisk
props.putAll(properties.filter { (key, _) -> key.startsWith("magisk.") })
// Load config.prop
val configPath: String? by this val configPath: String? by this
val config = rootFile(configPath ?: "config.prop") val configFile = rootFile(configPath ?: "config.prop")
if (config.exists()) if (configFile.exists()) {
config.inputStream().use { props.load(it) } configFile.inputStream().use {
val config = Properties()
config.load(it)
// Remove properties that should be passed by commandline
config.remove("abiList")
props.putAll(config)
}
}
// Commandline override
findProperty("abiList")?.let { props.put("abiList", it) }
val repo = FileRepository(rootFile(".git")) val repo = FileRepository(rootFile(".git"))
val refId = repo.refDatabase.exactRef("HEAD").objectId val refId = repo.refDatabase.exactRef("HEAD").objectId

View File

@@ -1,72 +1,68 @@
import com.android.build.api.artifact.SingleArtifact import com.android.build.api.artifact.SingleArtifact
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.dsl.CommonExtension
import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS import com.android.build.api.instrumentation.FramesComputationMode.COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS
import com.android.build.api.instrumentation.InstrumentationScope import com.android.build.api.instrumentation.InstrumentationScope
import com.android.build.api.variant.AndroidComponentsExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension 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 org.apache.tools.ant.filters.FixCrLfFilter import org.apache.tools.ant.filters.FixCrLfFilter
import org.gradle.api.Action import org.gradle.api.Action
import org.gradle.api.JavaVersion import org.gradle.api.JavaVersion
import org.gradle.api.Project import org.gradle.api.Project
import org.gradle.api.tasks.Copy import org.gradle.api.file.DirectoryProperty
import org.gradle.api.tasks.Delete import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.StopExecutionException import org.gradle.api.tasks.StopExecutionException
import org.gradle.api.tasks.Sync import org.gradle.api.tasks.Sync
import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.exclude import org.gradle.kotlin.dsl.exclude
import org.gradle.kotlin.dsl.filter import org.gradle.kotlin.dsl.filter
import org.gradle.kotlin.dsl.get import org.gradle.kotlin.dsl.get
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.register
import org.gradle.kotlin.dsl.withType import org.gradle.kotlin.dsl.withType
import org.jetbrains.kotlin.gradle.dsl.JvmTarget import org.jetbrains.kotlin.gradle.dsl.JvmTarget
import org.jetbrains.kotlin.gradle.tasks.KotlinCompile import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
import java.net.URI import java.net.URI
import java.security.MessageDigest import java.security.MessageDigest
import java.util.HexFormat import java.util.HexFormat
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
private fun Project.androidBase(configure: Action<BaseExtension>) = private fun Project.android(configure: Action<CommonExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
private fun Project.android(configure: Action<BaseAppModuleExtension>) = private fun Project.androidApp(configure: Action<ApplicationExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
internal val Project.androidApp: BaseAppModuleExtension internal val Project.androidApp: ApplicationExtension
get() = extensions["android"] as BaseAppModuleExtension get() = extensions["android"] as ApplicationExtension
private val Project.androidLib: LibraryExtension private fun Project.androidComponents(configure: Action<AndroidComponentsExtension<*, *, *>>) =
get() = extensions["android"] as LibraryExtension extensions.configure(AndroidComponentsExtension::class.java, configure)
internal val Project.androidComponents private val Project.androidComponents: AndroidComponentsExtension<*, *, *>
get() = extensions.getByType(ApplicationAndroidComponentsExtension::class.java) get() = extensions["androidComponents"] as AndroidComponentsExtension<*, *, *>
internal fun Project.androidAppComponents(configure: Action<ApplicationAndroidComponentsExtension>) =
extensions.configure(ApplicationAndroidComponentsExtension::class.java, configure)
fun Project.setupCommon() { fun Project.setupCommon() {
androidBase { android {
compileSdkVersion(36) compileSdk {
version = release(36)
}
buildToolsVersion = "36.0.0" buildToolsVersion = "36.0.0"
ndkPath = "$sdkDirectory/ndk/magisk" ndkPath = "${androidComponents.sdkComponents.sdkDirectory.get().asFile}/ndk/magisk"
ndkVersion = "29.0.14206865" ndkVersion = "29.0.14206865"
defaultConfig { defaultConfig.apply {
minSdk = 23 minSdk = 23
} }
compileOptions { compileOptions.apply {
sourceCompatibility = JavaVersion.VERSION_21 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21
} }
packagingOptions { packaging.apply {
resources { resources {
excludes += arrayOf( excludes += arrayOf(
"/META-INF/*", "/META-INF/*",
@@ -123,93 +119,108 @@ const val BUSYBOX_DOWNLOAD_URL =
const val BUSYBOX_ZIP_CHECKSUM = const val BUSYBOX_ZIP_CHECKSUM =
"b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951" "b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951"
private abstract class SyncWithDir : Sync() {
@get:OutputDirectory
abstract val outputFolder: DirectoryProperty
}
fun Project.setupCoreLib() { fun Project.setupCoreLib() {
setupCommon() setupCommon()
androidLib.libraryVariants.all { val abiList = Config.abiList
val variant = name
val variantCapped = name.replaceFirstChar { it.uppercase() }
val abiList = Config.abiList
val syncLibs = tasks.register("sync${variantCapped}JniLibs", Sync::class) { androidComponents {
into("src/$variant/jniLibs") onVariants { variant ->
for (abi in abiList) { val variantName = variant.name
into(abi) { val variantCapped = variantName.replaceFirstChar { it.uppercase() }
from(rootFile("native/out/$abi")) {
include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so") val syncLibs = tasks.register("sync${variantCapped}JniLibs", SyncWithDir::class) {
rename { if (it.endsWith(".so")) it else "lib$it.so" } outputFolder.set(layout.buildDirectory.dir("$variantName/jniLibs"))
into(outputFolder)
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
}
}
variant.sources.jniLibs?.let {
it.addGeneratedSourceDirectory(syncLibs, SyncWithDir::outputFolder)
}
val syncResources = tasks.register("sync${variantCapped}Resources", SyncWithDir::class) {
outputFolder.set(layout.buildDirectory.dir("$variantName/resources"))
into(outputFolder)
into("META-INF/com/google/android") {
from(rootFile("scripts/update_binary.sh")) {
rename { "update-binary" }
}
from(rootFile("scripts/flash_script.sh")) {
rename { "updater-script" }
} }
} }
} }
from(zipTree(downloadFile(BUSYBOX_DOWNLOAD_URL, BUSYBOX_ZIP_CHECKSUM)))
include(abiList.map { "$it/libbusybox.so" }) variant.sources.resources?.let {
onlyIf { it.addGeneratedSourceDirectory(syncResources, SyncWithDir::outputFolder)
if (inputs.sourceFiles.files.size != abiList.size * 6)
throw StopExecutionException("Please build binaries first! (./build.py binary)")
true
} }
}
tasks.getByPath("merge${variantCapped}JniLibFolders").dependsOn(syncLibs) val stubTask = tasks.getByPath(":stub:comment$variantCapped")
val syncAssets = tasks.register("sync${variantCapped}Assets", SyncWithDir::class) {
outputFolder.set(layout.buildDirectory.dir("$variantName/assets"))
into(outputFolder)
val syncResources = tasks.register("sync${variantCapped}Resources", Sync::class) { inputs.property("version", Config.version)
into("src/$variant/resources/META-INF/com/google/android") inputs.property("versionCode", Config.versionCode)
from(rootFile("scripts/update_binary.sh")) { from(rootFile("scripts")) {
rename { "update-binary" } include("util_functions.sh", "boot_patch.sh", "addon.d.sh",
} "app_functions.sh", "uninstaller.sh", "module_installer.sh")
from(rootFile("scripts/flash_script.sh")) { }
rename { "updater-script" } from(rootFile("tools/bootctl"))
} into("chromeos") {
} from(rootFile("tools/futility"))
from(rootFile("tools/keys")) {
processJavaResourcesProvider.configure { dependsOn(syncResources) } include("kernel_data_key.vbprivk", "kernel.keyblock")
}
val stubTask = tasks.getByPath(":stub:comment$variantCapped") }
val stubApk = stubTask.outputs.files.asFileTree.filter { from(stubTask) {
it.name.endsWith(".apk") include { it.name.endsWith(".apk") }
} rename { "stub.apk" }
}
val syncAssets = tasks.register("sync${variantCapped}Assets", Sync::class) { filesMatching("**/util_functions.sh") {
dependsOn(stubTask) filter {
inputs.property("version", Config.version) it.replace(
inputs.property("versionCode", Config.versionCode) "#MAGISK_VERSION_STUB",
into("src/$variant/assets") "MAGISK_VER='${Config.version}'\nMAGISK_VER_CODE=${Config.versionCode}"
from(rootFile("scripts")) { )
include("util_functions.sh", "boot_patch.sh", "addon.d.sh", }
"app_functions.sh", "uninstaller.sh", "module_installer.sh") filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
}
from(rootFile("tools/bootctl"))
into("chromeos") {
from(rootFile("tools/futility"))
from(rootFile("tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
} }
} }
from(stubApk) {
rename { "stub.apk" } variant.sources.assets?.let {
} it.addGeneratedSourceDirectory(syncAssets, SyncWithDir::outputFolder)
filesMatching("**/util_functions.sh") {
filter {
it.replace(
"#MAGISK_VERSION_STUB",
"MAGISK_VER='${Config.version}'\nMAGISK_VER_CODE=${Config.versionCode}"
)
}
filter<FixCrLfFilter>("eol" to FixCrLfFilter.CrLf.newInstance("lf"))
} }
} }
mergeAssetsProvider.configure { dependsOn(syncAssets) }
}
tasks.named<Delete>("clean") {
delete.addAll(listOf("src/main/jniLibs", "src/main/resources", "src/debug", "src/release"))
} }
} }
fun Project.setupAppCommon() { fun Project.setupAppCommon() {
setupCommon() setupCommon()
android { androidApp {
signingConfigs { signingConfigs {
Config["keyStore"]?.also { Config["keyStore"]?.also {
create("config") { create("config") {
@@ -254,22 +265,25 @@ fun Project.setupAppCommon() {
} }
} }
androidComponents.onVariants { variant -> androidAppComponents {
val commentTask = tasks.register( onVariants { variant ->
"comment${variant.name.replaceFirstChar { it.uppercase() }}", val commentTask = tasks.register(
AddCommentTask::class.java "comment${variant.name.replaceFirstChar { it.uppercase() }}",
) AddCommentTask::class.java
val transformationRequest = variant.artifacts.use(commentTask) )
.wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder) val transformationRequest = variant.artifacts.use(commentTask)
.toTransformMany(SingleArtifact.APK) .wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder)
val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig .toTransformMany(SingleArtifact.APK)
commentTask.configure { val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig
this.transformationRequest = transformationRequest commentTask.configure {
this.signingConfig = signingConfig this.transformationRequest = transformationRequest
this.comment = "version=${Config.version}\n" + this.signingConfig = signingConfig
"versionCode=${Config.versionCode}\n" + this.comment = "version=${Config.version}\n" +
"stubVersion=${Config.stubVersion}\n" "versionCode=${Config.versionCode}\n" +
this.outFolder.set(layout.buildDirectory.dir("outputs/apk/${variant.name}")) "stubVersion=${Config.stubVersion}\n"
this.outFolder.set(layout.buildDirectory.dir("outputs/apk/${variant.name}"))
}
} }
} }
} }
@@ -277,7 +291,7 @@ fun Project.setupAppCommon() {
fun Project.setupMainApk() { fun Project.setupMainApk() {
setupAppCommon() setupAppCommon()
android { androidApp {
namespace = "com.topjohnwu.magisk" namespace = "com.topjohnwu.magisk"
defaultConfig { defaultConfig {
@@ -290,8 +304,10 @@ fun Project.setupMainApk() {
debugSymbolLevel = "FULL" debugSymbolLevel = "FULL"
} }
} }
}
androidComponents.onVariants { variant -> androidComponents {
onVariants { variant ->
variant.instrumentation.apply { variant.instrumentation.apply {
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS) setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
transformClassesWith( transformClassesWith(
@@ -314,17 +330,26 @@ const val SHAMIKO_CHECKSUM =
fun Project.setupTestApk() { fun Project.setupTestApk() {
setupAppCommon() setupAppCommon()
androidApp.applicationVariants.all { androidComponents {
val variantCapped = name.replaceFirstChar { it.uppercase() } onVariants { variant ->
val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) { val variantName = variant.name
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) { val variantCapped = variantName.replaceFirstChar { it.uppercase() }
rename { "lsposed.zip" }
val dlTask = tasks.register("download${variantCapped}Lsposed", SyncWithDir::class) {
outputFolder.set(layout.buildDirectory.dir("$variantName/lsposed"))
into(outputFolder)
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
rename { "lsposed.zip" }
}
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
rename { "shamiko.zip" }
}
} }
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
rename { "shamiko.zip" } variant.sources.assets?.let {
it.addGeneratedSourceDirectory(dlTask, SyncWithDir::outputFolder)
} }
into("src/${this@all.name}/assets")
} }
mergeAssetsProvider.configure { dependsOn(dlTask) }
} }
} }

View File

@@ -8,13 +8,14 @@ import org.gradle.api.tasks.CacheableTask
import org.gradle.api.tasks.Delete import org.gradle.api.tasks.Delete
import org.gradle.api.tasks.Input import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.InputFiles import org.gradle.api.tasks.OutputDirectory
import org.gradle.api.tasks.OutputFile import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.PathSensitive import org.gradle.api.tasks.PathSensitive
import org.gradle.api.tasks.PathSensitivity import org.gradle.api.tasks.PathSensitivity
import org.gradle.api.tasks.TaskAction import org.gradle.api.tasks.TaskAction
import org.gradle.kotlin.dsl.assign import org.gradle.kotlin.dsl.assign
import org.gradle.kotlin.dsl.named import org.gradle.kotlin.dsl.named
import org.gradle.kotlin.dsl.register
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream import java.io.ByteArrayOutputStream
import java.io.File import java.io.File
@@ -82,18 +83,16 @@ private abstract class ManifestUpdater: DefaultTask() {
@get:Input @get:Input
abstract val applicationId: Property<String> abstract val applicationId: Property<String>
@get:Input
abstract val factoryClass: Property<String>
@get:Input
abstract val appClass: Property<String>
@get:InputFile @get:InputFile
@get:PathSensitive(PathSensitivity.RELATIVE) @get:PathSensitive(PathSensitivity.RELATIVE)
abstract val mergedManifest: RegularFileProperty 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 @get:OutputFile
abstract val outputManifest: RegularFileProperty abstract val outputManifest: RegularFileProperty
@@ -170,25 +169,18 @@ private abstract class ManifestUpdater: DefaultTask() {
// Shuffle the order of the components // Shuffle the order of the components
cmpList.shuffle(RANDOM) cmpList.shuffle(RANDOM)
val (factoryPkg, factoryClass) = factoryClassDir.asFileTree.firstNotNullOf {
it.parentFile!!.name to it.name.removeSuffix(".java")
}
val (appPkg, appClass) = appClassDir.asFileTree.firstNotNullOf {
it.parentFile!!.name to it.name.removeSuffix(".java")
}
val components = cmpList.joinToString("\n\n") val components = cmpList.joinToString("\n\n")
.replace("\${applicationId}", applicationId.get()) .replace("\${applicationId}", applicationId.get())
val manifest = mergedManifest.asFile.get().readText().replace(Regex(".*\\<application"), """ val manifest = mergedManifest.asFile.get().readText().replace(Regex(".*\\<application"), """
|<application |<application
| android:appComponentFactory="$factoryPkg.$factoryClass" | android:appComponentFactory="${factoryClass.get()}"
| android:name="$appPkg.$appClass"""".ind(1) | android:name="${appClass.get()}"""".ind(1)
).replace(Regex(".*\\<\\/application"), "$components\n </application") ).replace(Regex(".*\\<\\/application"), "$components\n </application")
outputManifest.get().asFile.writeText(manifest) outputManifest.get().asFile.writeText(manifest)
} }
} }
private fun genStubClasses(outDir: File): Pair<String, String> {
private fun genStubClasses(factoryOutDir: File, appOutDir: File) {
val classNameGenerator = sequence { val classNameGenerator = sequence {
fun notJavaKeyword(name: String) = when (name) { fun notJavaKeyword(name: String) = when (name) {
"do", "if", "for", "int", "new", "try" -> false "do", "if", "for", "int", "new", "try" -> false
@@ -217,7 +209,7 @@ private fun genStubClasses(factoryOutDir: File, appOutDir: File) {
} }
}.distinct().iterator() }.distinct().iterator()
fun genClass(type: String, outDir: File) { fun genClass(type: String, outDir: File): String {
val clzName = classNameGenerator.next() val clzName = classNameGenerator.next()
val (pkg, name) = clzName.split('.') val (pkg, name) = clzName.split('.')
val pkgDir = File(outDir, pkg) val pkgDir = File(outDir, pkg)
@@ -226,10 +218,12 @@ private fun genStubClasses(factoryOutDir: File, appOutDir: File) {
it.println("package $pkg;") it.println("package $pkg;")
it.println("public class $name extends com.topjohnwu.magisk.$type {}") it.println("public class $name extends com.topjohnwu.magisk.$type {}")
} }
return clzName
} }
genClass("DelegateComponentFactory", factoryOutDir) val factory = genClass("DelegateComponentFactory", outDir)
genClass("StubApplication", appOutDir) val app = genClass("StubApplication", outDir)
return Pair(factory, app)
} }
private fun genEncryptedResources(res: ByteArray, outDir: File) { private fun genEncryptedResources(res: ByteArray, outDir: File) {
@@ -264,74 +258,76 @@ private fun genEncryptedResources(res: ByteArray, outDir: File) {
} }
} }
private abstract class TaskWithDir : DefaultTask() {
@get:OutputDirectory
abstract val outputFolder: DirectoryProperty
}
fun Project.setupStubApk() { fun Project.setupStubApk() {
setupAppCommon() setupAppCommon()
androidComponents.onVariants { variant -> androidAppComponents {
val variantName = variant.name onVariants { variant ->
val variantCapped = variantName.replaceFirstChar { it.uppercase() } val variantName = variant.name
val manifestUpdater = val variantCapped = variantName.replaceFirstChar { it.uppercase() }
project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) { val variantLowered = variantName.lowercase()
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 componentJavaOutDir = layout.buildDirectory
val variantCapped = name.replaceFirstChar { it.uppercase() } .dir("generated/${variantLowered}/components").get().asFile
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") { val (factory, app) = genStubClasses(componentJavaOutDir)
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") { val manifestUpdater =
outputs.dir(outResDir) project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) {
doLast { applicationId = variant.applicationId
val apkTmp = File("${apk}.tmp") factoryClass.set(factory)
providers.exec { appClass.set(app)
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
}.result.get()
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() variant.artifacts.use(manifestUpdater)
genEncryptedResources(bos.toByteArray(), outResDir) .wiredWithFiles(
ManifestUpdater::mergedManifest,
ManifestUpdater::outputManifest)
.toTransform(SingleArtifact.MERGED_MANIFEST)
val aapt = sdkComponents.aapt2.get().executable.get().asFile
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
"${variantLowered}/process${variantCapped}Resources/" +
"linked-resources-binary-format-${variantLowered}.ap_").get().asFile
val genResourcesTask = tasks.register("generate${variantCapped}BundledResources", TaskWithDir::class) {
dependsOn("process${variantCapped}Resources")
outputFolder.set(layout.buildDirectory.dir("generated/${variantLowered}/resources"))
doLast {
val apkTmp = File("${apk}.tmp")
providers.exec {
commandLine(aapt, "optimize", "-o", apkTmp, "--collapse-resource-names", apk)
}.result.get()
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(), outputFolder.get().asFile)
}
}
variant.sources.java?.let {
it.addStaticSourceDirectory(componentJavaOutDir.path)
it.addGeneratedSourceDirectory(genResourcesTask, TaskWithDir::outputFolder)
} }
} }
registerJavaGeneratingTask(processResourcesTask, outResDir)
} }
// Override optimizeReleaseResources task // Override optimizeReleaseResources task
val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" + val apk = layout.buildDirectory.file("intermediates/linked_resources_binary_format/" +
"release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile "release/processReleaseResources/linked-resources-binary-format-release.ap_").get().asFile

3
app/core/.gitignore vendored
View File

@@ -1,3 +0,0 @@
/build
src/debug
src/release

View File

@@ -1,6 +1,5 @@
plugins { plugins {
id("com.android.library") id("com.android.library")
kotlin("android")
kotlin("plugin.parcelize") kotlin("plugin.parcelize")
id("dev.zacsweers.moshix") id("dev.zacsweers.moshix")
id("com.google.devtools.ksp") id("com.google.devtools.ksp")

View File

@@ -33,9 +33,5 @@
# is used. # is used.
-keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation -keep,allowobfuscation,allowshrinking class kotlin.coroutines.Continuation
# Excessive obfuscation
-flattenpackagehierarchy
-allowaccessmodification
-dontwarn org.junit.** -dontwarn org.junit.**
-dontwarn org.apache.** -dontwarn org.apache.**

View File

@@ -24,9 +24,7 @@ org.gradle.caching=true
kapt.use.k2=true kapt.use.k2=true
# Android # Android
android.useAndroidX=true
android.injected.testOnly=false android.injected.testOnly=false
android.nonFinalResIds=false
# Magisk # Magisk
magisk.stubVersion=40 magisk.stubVersion=40

View File

@@ -1,7 +1,7 @@
[versions] [versions]
kotlin = "2.2.21" kotlin = "2.3.0"
android = "8.13.2" android = "9.0.0"
ksp = "2.3.3" ksp = "2.3.4"
rikka = "1.3.0" rikka = "1.3.0"
navigation = "2.9.6" navigation = "2.9.6"
libsu = "6.0.0" libsu = "6.0.0"
@@ -23,7 +23,7 @@ timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" } jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
# AndroidX # AndroidX
activity = { module = "androidx.activity:activity", version = "1.12.1" } activity = { module = "androidx.activity:activity", version = "1.12.2" }
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" } appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.1" }
core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" } core-ktx = { module = "androidx.core:core-ktx", version = "1.17.0" }
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.2.0" } core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.2.0" }
@@ -37,7 +37,7 @@ room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" } room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" } room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.2.0" } swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.2.0" }
transition = { module = "androidx.transition:transition", version = "1.6.0" } transition = { module = "androidx.transition:transition", version = "1.7.0" }
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" } collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
material = { module = "com.google.android.material:material", version = "1.13.0" } material = { module = "com.google.android.material:material", version = "1.13.0" }
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" } jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
@@ -59,9 +59,10 @@ rikka-insets = { module = "dev.rikka.rikkax.insets:insets", version.ref = "rikka
# Build plugins # Build plugins
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" } android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" }
android-kapt-plugin = { module = "com.android.legacy-kapt:com.android.legacy-kapt.gradle.plugin", version.ref = "android" }
ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" } ksp-plugin = { module = "com.google.devtools.ksp:com.google.devtools.ksp.gradle.plugin", version.ref = "ksp" }
navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" } navigation-safe-args-plugin = { module = "androidx.navigation:navigation-safe-args-gradle-plugin", version.ref = "navigation" }
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" } lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.34.1" } moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.34.2" }
[plugins] [plugins]

View File

@@ -1,6 +1,6 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.3.0-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

View File

@@ -1 +0,0 @@
/build

View File

@@ -6,4 +6,5 @@ setupCommon()
android { android {
namespace = "com.topjohnwu.shared" namespace = "com.topjohnwu.shared"
enableKotlin = false
} }

View File

@@ -1,6 +1,5 @@
plugins { plugins {
id("com.android.application") id("com.android.application")
kotlin("android")
} }
android { android {

View File

@@ -406,6 +406,7 @@ def build_apk(module: str):
gradlew, gradlew,
f"{module}:assemble{build_type}", f"{module}:assemble{build_type}",
f"-PconfigPath={props}", f"-PconfigPath={props}",
f"-PabiList={','.join(build_abis.keys())}",
], ],
env=env, env=env,
) )

View File

@@ -292,7 +292,7 @@ static int find_dtb_offset(const uint8_t *buf, unsigned sz) {
auto fdt_hdr = reinterpret_cast<const fdt_header *>(curr); auto fdt_hdr = reinterpret_cast<const fdt_header *>(curr);
// Check that fdt_header.totalsize does not overflow kernel image size or is empty dtb // Check that fdt_header.totalsize does not overflow kernel image size or is empty dtb
// https://github.com/torvalds/linux/commit/7b937cc243e5b1df8780a0aa743ce800df6c68d1 // https://github.com/torvalds/linux/commit/7b937cc243e5b1df8780a0aa743ce800df6c68d1
uint32_t totalsize = fdt_hdr->totalsize; uint32_t totalsize = fdt_hdr->totalsize;
if (totalsize > end - curr || totalsize <= 0x48) if (totalsize > end - curr || totalsize <= 0x48)
continue; continue;
@@ -611,7 +611,9 @@ bool boot_img::parse_image(const uint8_t *addr, FileFormat type) {
int split_image_dtb(Utf8CStr filename, bool skip_decomp) { int split_image_dtb(Utf8CStr filename, bool skip_decomp) {
mmap_data img(filename.c_str()); mmap_data img(filename.c_str());
if (size_t off = find_dtb_offset(img.data(), img.size()); off > 0) { if (int offset = find_dtb_offset(img.data(), img.size()); offset > 0) {
size_t off = (size_t) offset;
FileFormat fmt = check_fmt_lg(img.data(), img.size()); FileFormat fmt = check_fmt_lg(img.data(), img.size());
if (!skip_decomp && fmt_compressed(fmt)) { if (!skip_decomp && fmt_compressed(fmt)) {
int fd = creat(KERNEL_FILE, 0644); int fd = creat(KERNEL_FILE, 0644);
@@ -913,6 +915,8 @@ void repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp) {
file_align(); file_align();
} }
off.tail = lseek(fd, 0, SEEK_CUR);
// Proprietary stuffs // Proprietary stuffs
if (boot.flags[SEANDROID_FLAG]) { if (boot.flags[SEANDROID_FLAG]) {
xwrite(fd, SEANDROID_MAGIC, 16); xwrite(fd, SEANDROID_MAGIC, 16);
@@ -923,7 +927,6 @@ void repack(Utf8CStr src_img, Utf8CStr out_img, bool skip_comp) {
xwrite(fd, LG_BUMP_MAGIC, 16); xwrite(fd, LG_BUMP_MAGIC, 16);
} }
off.tail = lseek(fd, 0, SEEK_CUR);
file_align(); file_align();
// vbmeta // vbmeta