Compare commits

..

2 Commits

Author SHA1 Message Date
Wang Han
30cd19ec9c Fix formatting and update comments in bootimg.cpp 2026-01-17 16:35:41 +08:00
Wang Han
1f1eb01a7d magiskboot: Fix tail offset calculation 2026-01-17 14:38:51 +08:00
20 changed files with 244 additions and 276 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 Normal file
View File

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

View File

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

View File

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

1
app/buildSrc/.gitignore vendored Normal file
View File

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

View File

@@ -1,3 +1,5 @@
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
plugins { plugins {
`kotlin-dsl` `kotlin-dsl`
} }
@@ -19,7 +21,6 @@ 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,25 +46,11 @@ 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 configFile = rootFile(configPath ?: "config.prop") val config = rootFile(configPath ?: "config.prop")
if (configFile.exists()) { if (config.exists())
configFile.inputStream().use { config.inputStream().use { props.load(it) }
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,68 +1,72 @@
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.file.DirectoryProperty import org.gradle.api.tasks.Copy
import org.gradle.api.tasks.OutputDirectory import org.gradle.api.tasks.Delete
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.android(configure: Action<CommonExtension>) = private fun Project.androidBase(configure: Action<BaseExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
private fun Project.androidApp(configure: Action<ApplicationExtension>) = private fun Project.android(configure: Action<BaseAppModuleExtension>) =
extensions.configure("android", configure) extensions.configure("android", configure)
internal val Project.androidApp: ApplicationExtension internal val Project.androidApp: BaseAppModuleExtension
get() = extensions["android"] as ApplicationExtension get() = extensions["android"] as BaseAppModuleExtension
private fun Project.androidComponents(configure: Action<AndroidComponentsExtension<*, *, *>>) = private val Project.androidLib: LibraryExtension
extensions.configure(AndroidComponentsExtension::class.java, configure) get() = extensions["android"] as LibraryExtension
private val Project.androidComponents: AndroidComponentsExtension<*, *, *> internal val Project.androidComponents
get() = extensions["androidComponents"] as AndroidComponentsExtension<*, *, *> get() = extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
internal fun Project.androidAppComponents(configure: Action<ApplicationAndroidComponentsExtension>) =
extensions.configure(ApplicationAndroidComponentsExtension::class.java, configure)
fun Project.setupCommon() { fun Project.setupCommon() {
android { androidBase {
compileSdk { compileSdkVersion(36)
version = release(36)
}
buildToolsVersion = "36.0.0" buildToolsVersion = "36.0.0"
ndkPath = "${androidComponents.sdkComponents.sdkDirectory.get().asFile}/ndk/magisk" ndkPath = "$sdkDirectory/ndk/magisk"
ndkVersion = "29.0.14206865" ndkVersion = "29.0.14206865"
defaultConfig.apply { defaultConfig {
minSdk = 23 minSdk = 23
} }
compileOptions.apply { compileOptions {
sourceCompatibility = JavaVersion.VERSION_21 sourceCompatibility = JavaVersion.VERSION_21
targetCompatibility = JavaVersion.VERSION_21 targetCompatibility = JavaVersion.VERSION_21
} }
packaging.apply { packagingOptions {
resources { resources {
excludes += arrayOf( excludes += arrayOf(
"/META-INF/*", "/META-INF/*",
@@ -119,108 +123,93 @@ 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()
val abiList = Config.abiList androidLib.libraryVariants.all {
val variant = name
val variantCapped = name.replaceFirstChar { it.uppercase() }
val abiList = Config.abiList
androidComponents { val syncLibs = tasks.register("sync${variantCapped}JniLibs", Sync::class) {
onVariants { variant -> into("src/$variant/jniLibs")
val variantName = variant.name for (abi in abiList) {
val variantCapped = variantName.replaceFirstChar { it.uppercase() } into(abi) {
from(rootFile("native/out/$abi")) {
val syncLibs = tasks.register("sync${variantCapped}JniLibs", SyncWithDir::class) { include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so")
outputFolder.set(layout.buildDirectory.dir("$variantName/jniLibs")) rename { if (it.endsWith(".so")) it else "lib$it.so" }
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)))
variant.sources.resources?.let { include(abiList.map { "$it/libbusybox.so" })
it.addGeneratedSourceDirectory(syncResources, SyncWithDir::outputFolder) onlyIf {
} if (inputs.sourceFiles.files.size != abiList.size * 6)
throw StopExecutionException("Please build binaries first! (./build.py binary)")
val stubTask = tasks.getByPath(":stub:comment$variantCapped") true
val syncAssets = tasks.register("sync${variantCapped}Assets", SyncWithDir::class) {
outputFolder.set(layout.buildDirectory.dir("$variantName/assets"))
into(outputFolder)
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
from(rootFile("scripts")) {
include("util_functions.sh", "boot_patch.sh", "addon.d.sh",
"app_functions.sh", "uninstaller.sh", "module_installer.sh")
}
from(rootFile("tools/bootctl"))
into("chromeos") {
from(rootFile("tools/futility"))
from(rootFile("tools/keys")) {
include("kernel_data_key.vbprivk", "kernel.keyblock")
}
}
from(stubTask) {
include { it.name.endsWith(".apk") }
rename { "stub.apk" }
}
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"))
}
}
variant.sources.assets?.let {
it.addGeneratedSourceDirectory(syncAssets, SyncWithDir::outputFolder)
} }
} }
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" }
}
}
processJavaResourcesProvider.configure { dependsOn(syncResources) }
val stubTask = tasks.getByPath(":stub:comment$variantCapped")
val stubApk = stubTask.outputs.files.asFileTree.filter {
it.name.endsWith(".apk")
}
val syncAssets = tasks.register("sync${variantCapped}Assets", Sync::class) {
dependsOn(stubTask)
inputs.property("version", Config.version)
inputs.property("versionCode", Config.versionCode)
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")
}
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" }
}
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()
androidApp { android {
signingConfigs { signingConfigs {
Config["keyStore"]?.also { Config["keyStore"]?.also {
create("config") { create("config") {
@@ -265,25 +254,22 @@ fun Project.setupAppCommon() {
} }
} }
androidAppComponents { androidComponents.onVariants { variant ->
onVariants { variant -> val commentTask = tasks.register(
val commentTask = tasks.register( "comment${variant.name.replaceFirstChar { it.uppercase() }}",
"comment${variant.name.replaceFirstChar { it.uppercase() }}", AddCommentTask::class.java
AddCommentTask::class.java )
) val transformationRequest = variant.artifacts.use(commentTask)
val transformationRequest = variant.artifacts.use(commentTask) .wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder)
.wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder) .toTransformMany(SingleArtifact.APK)
.toTransformMany(SingleArtifact.APK) val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig
val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig commentTask.configure {
commentTask.configure { this.transformationRequest = transformationRequest
this.transformationRequest = transformationRequest this.signingConfig = signingConfig
this.signingConfig = signingConfig this.comment = "version=${Config.version}\n" +
this.comment = "version=${Config.version}\n" + "versionCode=${Config.versionCode}\n" +
"versionCode=${Config.versionCode}\n" + "stubVersion=${Config.stubVersion}\n"
"stubVersion=${Config.stubVersion}\n" this.outFolder.set(layout.buildDirectory.dir("outputs/apk/${variant.name}"))
this.outFolder.set(layout.buildDirectory.dir("outputs/apk/${variant.name}"))
}
} }
} }
} }
@@ -291,7 +277,7 @@ fun Project.setupAppCommon() {
fun Project.setupMainApk() { fun Project.setupMainApk() {
setupAppCommon() setupAppCommon()
androidApp { android {
namespace = "com.topjohnwu.magisk" namespace = "com.topjohnwu.magisk"
defaultConfig { defaultConfig {
@@ -304,10 +290,8 @@ fun Project.setupMainApk() {
debugSymbolLevel = "FULL" debugSymbolLevel = "FULL"
} }
} }
}
androidComponents { androidComponents.onVariants { variant ->
onVariants { variant ->
variant.instrumentation.apply { variant.instrumentation.apply {
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS) setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
transformClassesWith( transformClassesWith(
@@ -330,26 +314,17 @@ const val SHAMIKO_CHECKSUM =
fun Project.setupTestApk() { fun Project.setupTestApk() {
setupAppCommon() setupAppCommon()
androidComponents { androidApp.applicationVariants.all {
onVariants { variant -> val variantCapped = name.replaceFirstChar { it.uppercase() }
val variantName = variant.name val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) {
val variantCapped = variantName.replaceFirstChar { it.uppercase() } from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
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)) {
variant.sources.assets?.let { rename { "shamiko.zip" }
it.addGeneratedSourceDirectory(dlTask, SyncWithDir::outputFolder)
} }
into("src/${this@all.name}/assets")
} }
mergeAssetsProvider.configure { dependsOn(dlTask) }
} }
} }

View File

@@ -8,14 +8,13 @@ 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.OutputDirectory import org.gradle.api.tasks.InputFiles
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
@@ -83,16 +82,18 @@ 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
@@ -169,18 +170,25 @@ 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="${factoryClass.get()}" | android:appComponentFactory="$factoryPkg.$factoryClass"
| android:name="${appClass.get()}"""".ind(1) | android:name="$appPkg.$appClass"""".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
@@ -209,7 +217,7 @@ private fun genStubClasses(outDir: File): Pair<String, String> {
} }
}.distinct().iterator() }.distinct().iterator()
fun genClass(type: String, outDir: File): String { fun genClass(type: String, outDir: File) {
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)
@@ -218,12 +226,10 @@ private fun genStubClasses(outDir: File): Pair<String, String> {
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
} }
val factory = genClass("DelegateComponentFactory", outDir) genClass("DelegateComponentFactory", factoryOutDir)
val app = genClass("StubApplication", outDir) genClass("StubApplication", appOutDir)
return Pair(factory, app)
} }
private fun genEncryptedResources(res: ByteArray, outDir: File) { private fun genEncryptedResources(res: ByteArray, outDir: File) {
@@ -258,76 +264,74 @@ 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()
androidAppComponents { androidComponents.onVariants { variant ->
onVariants { variant -> val variantName = variant.name
val variantName = variant.name val variantCapped = variantName.replaceFirstChar { it.uppercase() }
val variantCapped = variantName.replaceFirstChar { it.uppercase() } val manifestUpdater =
val variantLowered = variantName.lowercase() project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) {
dependsOn("generate${variantCapped}ObfuscatedClass")
val componentJavaOutDir = layout.buildDirectory applicationId = variant.applicationId
.dir("generated/${variantLowered}/components").get().asFile appClassDir.set(layout.buildDirectory.dir("generated/source/app/$variantName"))
factoryClassDir.set(layout.buildDirectory.dir("generated/source/factory/$variantName"))
val (factory, app) = genStubClasses(componentJavaOutDir)
val manifestUpdater =
project.tasks.register("${variantName}ManifestProducer", ManifestUpdater::class.java) {
applicationId = variant.applicationId
factoryClass.set(factory)
appClass.set(app)
}
variant.artifacts.use(manifestUpdater)
.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.artifacts.use(manifestUpdater)
variant.sources.java?.let { .wiredWithFiles(
it.addStaticSourceDirectory(componentJavaOutDir.path) ManifestUpdater::mergedManifest,
it.addGeneratedSourceDirectory(genResourcesTask, TaskWithDir::outputFolder) 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")
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(), outResDir)
}
}
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 Normal file
View File

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

View File

@@ -1,5 +1,6 @@
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,5 +33,9 @@
# 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,7 +24,9 @@ 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.3.0" kotlin = "2.2.21"
android = "9.0.0" android = "8.13.2"
ksp = "2.3.4" ksp = "2.3.3"
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.2" } activity = { module = "androidx.activity:activity", version = "1.12.1" }
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.7.0" } transition = { module = "androidx.transition:transition", version = "1.6.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,10 +59,9 @@ 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.2" } moshi-plugin = { module = "dev.zacsweers.moshix:dev.zacsweers.moshix.gradle.plugin", version = "0.34.1" }
[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.3.0-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-9.0.0-bin.zip
networkTimeout=10000 networkTimeout=10000
validateDistributionUrl=true validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME

1
app/shared/.gitignore vendored Normal file
View File

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

View File

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

View File

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

View File

@@ -406,7 +406,6 @@ 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,9 +611,7 @@ 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 (int offset = find_dtb_offset(img.data(), img.size()); offset > 0) { if (size_t off = find_dtb_offset(img.data(), img.size()); off > 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);
@@ -915,8 +913,6 @@ 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);
@@ -927,6 +923,7 @@ 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