You've already forked Magisk
mirror of
https://github.com/topjohnwu/Magisk.git
synced 2025-09-06 06:36:58 +00:00
Move all gradle files into folder app
Decouple java and native projects
This commit is contained in:
7
app/.gitignore
vendored
Normal file
7
app/.gitignore
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
/dict.txt
|
||||
|
||||
# Gradle
|
||||
.gradle
|
||||
.kotlin
|
||||
/local.properties
|
||||
/build
|
||||
@@ -35,7 +35,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:core"))
|
||||
implementation(project(":core"))
|
||||
coreLibraryDesugaring(libs.jdk.libs)
|
||||
|
||||
implementation(libs.indeterminate.checkbox)
|
||||
|
||||
@@ -1,5 +1,11 @@
|
||||
tasks.register("clean") {
|
||||
plugins {
|
||||
id("MagiskPlugin")
|
||||
}
|
||||
|
||||
tasks.register("clean", Delete::class) {
|
||||
delete(rootProject.layout.buildDirectory)
|
||||
|
||||
subprojects.forEach {
|
||||
dependsOn(":app:${it.name}:clean")
|
||||
dependsOn(":${it.name}:clean")
|
||||
}
|
||||
}
|
||||
|
||||
1
app/buildSrc/.gitignore
vendored
Normal file
1
app/buildSrc/.gitignore
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/build
|
||||
34
app/buildSrc/build.gradle.kts
Normal file
34
app/buildSrc/build.gradle.kts
Normal file
@@ -0,0 +1,34 @@
|
||||
import org.jetbrains.kotlin.gradle.dsl.KotlinVersion
|
||||
|
||||
plugins {
|
||||
`kotlin-dsl`
|
||||
}
|
||||
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
gradlePlugin {
|
||||
plugins {
|
||||
register("MagiskPlugin") {
|
||||
id = "MagiskPlugin"
|
||||
implementationClass = "MagiskPlugin"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
kotlin {
|
||||
compilerOptions {
|
||||
languageVersion = KotlinVersion.KOTLIN_2_0
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(kotlin("gradle-plugin", libs.versions.kotlin.get()))
|
||||
implementation(libs.android.gradle.plugin)
|
||||
implementation(libs.ksp.plugin)
|
||||
implementation(libs.navigation.safe.args.plugin)
|
||||
implementation(libs.lsparanoid.plugin)
|
||||
implementation(libs.jgit)
|
||||
}
|
||||
7
app/buildSrc/settings.gradle.kts
Normal file
7
app/buildSrc/settings.gradle.kts
Normal file
@@ -0,0 +1,7 @@
|
||||
dependencyResolutionManagement {
|
||||
versionCatalogs {
|
||||
create("libs") {
|
||||
from(files("../gradle/libs.versions.toml"))
|
||||
}
|
||||
}
|
||||
}
|
||||
261
app/buildSrc/src/main/java/Codegen.kt
Normal file
261
app/buildSrc/src/main/java/Codegen.kt
Normal file
@@ -0,0 +1,261 @@
|
||||
import org.gradle.api.DefaultTask
|
||||
import org.gradle.api.file.DirectoryProperty
|
||||
import org.gradle.api.file.RegularFileProperty
|
||||
import org.gradle.api.provider.Property
|
||||
import org.gradle.api.tasks.CacheableTask
|
||||
import org.gradle.api.tasks.Input
|
||||
import org.gradle.api.tasks.InputFile
|
||||
import org.gradle.api.tasks.InputFiles
|
||||
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 java.io.ByteArrayInputStream
|
||||
import java.io.ByteArrayOutputStream
|
||||
import java.io.File
|
||||
import java.io.PrintStream
|
||||
import java.security.SecureRandom
|
||||
import java.util.Random
|
||||
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<String>()
|
||||
private val c2 = mutableListOf<String>()
|
||||
private val c3 = mutableListOf<String>()
|
||||
|
||||
fun initRandom(dict: File) {
|
||||
RANDOM = if (RAND_SEED != 0) Random(RAND_SEED.toLong()) else SecureRandom()
|
||||
c1.clear()
|
||||
c2.clear()
|
||||
c3.clear()
|
||||
for (a in chain('a'..'z', 'A'..'Z')) {
|
||||
if (a != 'a' && a != 'A') {
|
||||
c1.add("$a")
|
||||
}
|
||||
for (b in chain('a'..'z', 'A'..'Z', '0'..'9')) {
|
||||
c2.add("$a$b")
|
||||
for (c in chain('a'..'z', 'A'..'Z', '0'..'9')) {
|
||||
c3.add("$a$b$c")
|
||||
}
|
||||
}
|
||||
}
|
||||
c1.shuffle(RANDOM)
|
||||
c2.shuffle(RANDOM)
|
||||
c3.shuffle(RANDOM)
|
||||
PrintStream(dict).use {
|
||||
for (c in chain(c1, c2, c3)) {
|
||||
it.println(c)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <T> chain(vararg iters: Iterable<T>) = sequence {
|
||||
iters.forEach { it.forEach { v -> yield(v) } }
|
||||
}
|
||||
|
||||
private fun PrintStream.byteField(name: String, bytes: ByteArray) {
|
||||
println("public static byte[] $name() {")
|
||||
print("byte[] buf = {")
|
||||
print(bytes.joinToString(",") { it.toString() })
|
||||
println("};")
|
||||
println("return buf;")
|
||||
println("}")
|
||||
}
|
||||
|
||||
@CacheableTask
|
||||
abstract class ManifestUpdater: DefaultTask() {
|
||||
@get:Input
|
||||
abstract val applicationId: Property<String>
|
||||
|
||||
@get:InputFile
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val mergedManifest: RegularFileProperty
|
||||
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val factoryClassDir: DirectoryProperty
|
||||
|
||||
@get:InputFiles
|
||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
||||
abstract val appClassDir: DirectoryProperty
|
||||
|
||||
@get:OutputFile
|
||||
abstract val outputManifest: RegularFileProperty
|
||||
|
||||
@TaskAction
|
||||
fun taskAction() {
|
||||
fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level))
|
||||
|
||||
val cmpList = mutableListOf<String>()
|
||||
|
||||
cmpList.add("""
|
||||
|<provider
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_0"
|
||||
| android:authorities="${'$'}{applicationId}.provider"
|
||||
| android:directBootAware="true"
|
||||
| android:exported="false"
|
||||
| android:grantUriPermissions="true" />""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add("""
|
||||
|<receiver
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_1"
|
||||
| android:exported="false">
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.LOCALE_CHANGED" />
|
||||
| <action android:name="android.intent.action.UID_REMOVED" />
|
||||
| <action android:name="android.intent.action.MY_PACKAGE_REPLACED" />
|
||||
| </intent-filter>
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.PACKAGE_REPLACED" />
|
||||
| <action android:name="android.intent.action.PACKAGE_FULLY_REMOVED" />
|
||||
|
|
||||
| <data android:scheme="package" />
|
||||
| </intent-filter>
|
||||
|</receiver>""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add("""
|
||||
|<activity
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_2"
|
||||
| android:exported="true">
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.MAIN" />
|
||||
| <category android:name="android.intent.category.LAUNCHER" />
|
||||
| </intent-filter>
|
||||
|</activity>""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add("""
|
||||
|<activity
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_3"
|
||||
| android:directBootAware="true"
|
||||
| android:exported="false"
|
||||
| android:taskAffinity="">
|
||||
| <intent-filter>
|
||||
| <action android:name="android.intent.action.VIEW"/>
|
||||
| <category android:name="android.intent.category.DEFAULT"/>
|
||||
| </intent-filter>
|
||||
|</activity>""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add("""
|
||||
|<service
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_4"
|
||||
| android:exported="false"
|
||||
| android:foregroundServiceType="dataSync" />""".ind(2)
|
||||
)
|
||||
|
||||
cmpList.add("""
|
||||
|<service
|
||||
| android:name="x.COMPONENT_PLACEHOLDER_5"
|
||||
| android:exported="false"
|
||||
| android:permission="android.permission.BIND_JOB_SERVICE" />""".ind(2)
|
||||
)
|
||||
|
||||
// Shuffle the order of the components
|
||||
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")
|
||||
.replace("\${applicationId}", applicationId.get())
|
||||
val manifest = mergedManifest.asFile.get().readText().replace(Regex(".*\\<application"), """
|
||||
|<application
|
||||
| android:appComponentFactory="$factoryPkg.$factoryClass"
|
||||
| android:name="$appPkg.$appClass"""".ind(1)
|
||||
).replace(Regex(".*\\<\\/application"), "$components\n </application")
|
||||
outputManifest.get().asFile.writeText(manifest)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
fun genStubClasses(factoryOutDir: File, appOutDir: File) {
|
||||
fun String.ind(level: Int) = replaceIndentByMargin(" ".repeat(level))
|
||||
|
||||
val classNameGenerator = sequence {
|
||||
fun notJavaKeyword(name: String) = when (name) {
|
||||
"do", "if", "for", "int", "new", "try" -> false
|
||||
else -> true
|
||||
}
|
||||
|
||||
fun List<String>.process() = asSequence()
|
||||
.filter(::notJavaKeyword)
|
||||
// Distinct by lower case to support case insensitive file systems
|
||||
.distinctBy { it.lowercase() }
|
||||
|
||||
val names = mutableListOf<String>()
|
||||
names.addAll(c1)
|
||||
names.addAll(c2.process().take(30))
|
||||
names.addAll(c3.process().take(30))
|
||||
names.shuffle(RANDOM)
|
||||
|
||||
while (true) {
|
||||
val cls = StringBuilder()
|
||||
cls.append(names.random(kRANDOM))
|
||||
cls.append('.')
|
||||
cls.append(names.random(kRANDOM))
|
||||
// Old Android does not support capitalized package names
|
||||
// Check Android 7.0.0 PackageParser#buildClassName
|
||||
yield(cls.toString().replaceFirstChar { it.lowercase() })
|
||||
}
|
||||
}.distinct().iterator()
|
||||
|
||||
fun genClass(type: String, outDir: File) {
|
||||
val clzName = classNameGenerator.next()
|
||||
val (pkg, name) = clzName.split('.')
|
||||
val pkgDir = File(outDir, pkg)
|
||||
pkgDir.mkdirs()
|
||||
PrintStream(File(pkgDir, "$name.java")).use {
|
||||
it.println("package $pkg;")
|
||||
it.println("public class $name extends com.topjohnwu.magisk.$type {}")
|
||||
}
|
||||
}
|
||||
|
||||
genClass("DelegateComponentFactory", factoryOutDir)
|
||||
genClass("StubApplication", appOutDir)
|
||||
}
|
||||
|
||||
fun genEncryptedResources(res: ByteArray, outDir: File) {
|
||||
val mainPkgDir = File(outDir, "com/topjohnwu/magisk")
|
||||
mainPkgDir.mkdirs()
|
||||
|
||||
// Generate iv and key
|
||||
val iv = ByteArray(16)
|
||||
val key = ByteArray(32)
|
||||
RANDOM.nextBytes(iv)
|
||||
RANDOM.nextBytes(key)
|
||||
|
||||
val cipher = Cipher.getInstance("AES/CBC/PKCS5Padding")
|
||||
cipher.init(Cipher.ENCRYPT_MODE, SecretKeySpec(key, "AES"), IvParameterSpec(iv))
|
||||
val bos = ByteArrayOutputStream()
|
||||
|
||||
ByteArrayInputStream(res).use {
|
||||
CipherOutputStream(bos, cipher).use { os ->
|
||||
it.transferTo(os)
|
||||
}
|
||||
}
|
||||
|
||||
PrintStream(File(mainPkgDir, "Bytes.java")).use {
|
||||
it.println("package com.topjohnwu.magisk;")
|
||||
it.println("public final class Bytes {")
|
||||
|
||||
it.byteField("key", key)
|
||||
it.byteField("iv", iv)
|
||||
it.byteField("res", bos.toByteArray())
|
||||
|
||||
it.println("}")
|
||||
}
|
||||
}
|
||||
122
app/buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
122
app/buildSrc/src/main/java/DesugarClassVisitorFactory.kt
Normal file
@@ -0,0 +1,122 @@
|
||||
import com.android.build.api.instrumentation.AsmClassVisitorFactory
|
||||
import com.android.build.api.instrumentation.ClassContext
|
||||
import com.android.build.api.instrumentation.ClassData
|
||||
import com.android.build.api.instrumentation.InstrumentationParameters
|
||||
import org.objectweb.asm.ClassVisitor
|
||||
import org.objectweb.asm.MethodVisitor
|
||||
import org.objectweb.asm.Opcodes
|
||||
import org.objectweb.asm.Opcodes.ASM9
|
||||
|
||||
private const val DESUGAR_CLASS_NAME = "com.topjohnwu.magisk.core.utils.Desugar"
|
||||
private const val ZIP_ENTRY_CLASS_NAME = "java.util.zip.ZipEntry"
|
||||
private const val ZIP_OUT_STREAM_CLASS_NAME = "org.apache.commons.compress.archivers.zip.ZipArchiveOutputStream"
|
||||
private const val ZIP_UTIL_CLASS_NAME = "org/apache/commons/compress/archivers/zip/ZipUtil"
|
||||
private const val ZIP_ENTRY_GET_TIME_DESC = "()Ljava/nio/file/attribute/FileTime;"
|
||||
private const val DESUGAR_GET_TIME_DESC =
|
||||
"(Ljava/util/zip/ZipEntry;)Ljava/nio/file/attribute/FileTime;"
|
||||
|
||||
private fun ClassData.isTypeOf(name: String) = className == name || superClasses.contains(name)
|
||||
|
||||
abstract class DesugarClassVisitorFactory : AsmClassVisitorFactory<InstrumentationParameters.None> {
|
||||
override fun createClassVisitor(
|
||||
classContext: ClassContext,
|
||||
nextClassVisitor: ClassVisitor
|
||||
): ClassVisitor {
|
||||
return if (classContext.currentClassData.className == ZIP_OUT_STREAM_CLASS_NAME) {
|
||||
ZipEntryPatcher(classContext, ZipOutputStreamPatcher(nextClassVisitor))
|
||||
} else {
|
||||
ZipEntryPatcher(classContext, nextClassVisitor)
|
||||
}
|
||||
}
|
||||
|
||||
override fun isInstrumentable(classData: ClassData) = classData.className != DESUGAR_CLASS_NAME
|
||||
|
||||
// Patch ALL references to ZipEntry#getXXXTime
|
||||
class ZipEntryPatcher(
|
||||
private val classContext: ClassContext,
|
||||
cv: ClassVisitor
|
||||
) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String?,
|
||||
descriptor: String?,
|
||||
signature: String?,
|
||||
exceptions: Array<out String>?
|
||||
) = MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
|
||||
inner class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (!process(owner, name, descriptor)) {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
|
||||
private fun process(owner: String, name: String, descriptor: String): Boolean {
|
||||
val classData = classContext.loadClassData(owner.replace("/", ".")) ?: return false
|
||||
if (!classData.isTypeOf(ZIP_ENTRY_CLASS_NAME))
|
||||
return false
|
||||
if (descriptor != ZIP_ENTRY_GET_TIME_DESC)
|
||||
return false
|
||||
return when (name) {
|
||||
"getLastModifiedTime", "getLastAccessTime", "getCreationTime" -> {
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
DESUGAR_GET_TIME_DESC,
|
||||
false
|
||||
)
|
||||
true
|
||||
}
|
||||
else -> false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Patch ZipArchiveOutputStream#copyFromZipInputStream
|
||||
class ZipOutputStreamPatcher(cv: ClassVisitor) : ClassVisitor(ASM9, cv) {
|
||||
override fun visitMethod(
|
||||
access: Int,
|
||||
name: String,
|
||||
descriptor: String,
|
||||
signature: String?,
|
||||
exceptions: Array<out String?>?
|
||||
): MethodVisitor? {
|
||||
return if (name == "copyFromZipInputStream") {
|
||||
MethodPatcher(super.visitMethod(access, name, descriptor, signature, exceptions))
|
||||
} else {
|
||||
super.visitMethod(access, name, descriptor, signature, exceptions)
|
||||
}
|
||||
}
|
||||
|
||||
class MethodPatcher(mv: MethodVisitor?) : MethodVisitor(ASM9, mv) {
|
||||
override fun visitMethodInsn(
|
||||
opcode: Int,
|
||||
owner: String,
|
||||
name: String,
|
||||
descriptor: String?,
|
||||
isInterface: Boolean
|
||||
) {
|
||||
if (owner == ZIP_UTIL_CLASS_NAME && name == "checkRequestedFeatures") {
|
||||
// Redirect
|
||||
mv.visitMethodInsn(
|
||||
Opcodes.INVOKESTATIC,
|
||||
DESUGAR_CLASS_NAME.replace('.', '/'),
|
||||
name,
|
||||
descriptor,
|
||||
false
|
||||
)
|
||||
} else {
|
||||
super.visitMethodInsn(opcode, owner, name, descriptor, isInterface)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
49
app/buildSrc/src/main/java/Plugin.kt
Normal file
49
app/buildSrc/src/main/java/Plugin.kt
Normal file
@@ -0,0 +1,49 @@
|
||||
|
||||
import org.eclipse.jgit.internal.storage.file.FileRepository
|
||||
import org.gradle.api.Plugin
|
||||
import org.gradle.api.Project
|
||||
import org.gradle.kotlin.dsl.provideDelegate
|
||||
import java.io.File
|
||||
import java.util.Properties
|
||||
|
||||
private val props = Properties()
|
||||
private var commitHash = ""
|
||||
private val supportAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64", "riscv64")
|
||||
private val defaultAbis = setOf("armeabi-v7a", "x86", "arm64-v8a", "x86_64")
|
||||
|
||||
object Config {
|
||||
operator fun get(key: String): String? {
|
||||
val v = props[key] as? String ?: return null
|
||||
return v.ifBlank { null }
|
||||
}
|
||||
|
||||
fun contains(key: String) = get(key) != null
|
||||
|
||||
val version: String get() = get("version") ?: commitHash
|
||||
val versionCode: Int get() = get("magisk.versionCode")!!.toInt()
|
||||
val stubVersion: String get() = get("magisk.stubVersion")!!
|
||||
val abiList: Set<String> get() {
|
||||
val abiList = get("abiList") ?: return defaultAbis
|
||||
return abiList.split(Regex("\\s*,\\s*")).toSet() intersect supportAbis
|
||||
}
|
||||
}
|
||||
|
||||
val Project.baseDir: File get() = rootProject.file("..")
|
||||
|
||||
class MagiskPlugin : Plugin<Project> {
|
||||
override fun apply(project: Project) = project.applyPlugin()
|
||||
|
||||
private fun Project.applyPlugin() {
|
||||
initRandom(rootProject.file("dict.txt"))
|
||||
props.clear()
|
||||
rootProject.file("gradle.properties").inputStream().use { props.load(it) }
|
||||
val configPath: String? by this
|
||||
val config = configPath?.let { File(it) } ?: File(baseDir, "config.prop")
|
||||
if (config.exists())
|
||||
config.inputStream().use { props.load(it) }
|
||||
|
||||
val repo = FileRepository(File(baseDir, ".git"))
|
||||
val refId = repo.refDatabase.exactRef("HEAD").objectId
|
||||
commitHash = repo.newObjectReader().abbreviate(refId, 8).name()
|
||||
}
|
||||
}
|
||||
497
app/buildSrc/src/main/java/Setup.kt
Normal file
497
app/buildSrc/src/main/java/Setup.kt
Normal file
@@ -0,0 +1,497 @@
|
||||
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
|
||||
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.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
|
||||
import java.util.zip.ZipFile
|
||||
import java.util.zip.ZipOutputStream
|
||||
|
||||
private fun Project.androidBase(configure: Action<BaseExtension>) =
|
||||
extensions.configure("android", configure)
|
||||
|
||||
private fun Project.android(configure: Action<BaseAppModuleExtension>) =
|
||||
extensions.configure("android", configure)
|
||||
|
||||
private val Project.androidApp: BaseAppModuleExtension
|
||||
get() = extensions["android"] as BaseAppModuleExtension
|
||||
|
||||
private val Project.androidLib: LibraryExtension
|
||||
get() = extensions["android"] as LibraryExtension
|
||||
|
||||
private val Project.androidComponents
|
||||
get() = extensions.getByType(ApplicationAndroidComponentsExtension::class.java)
|
||||
|
||||
fun Project.setupCommon() {
|
||||
androidBase {
|
||||
compileSdkVersion(36)
|
||||
buildToolsVersion = "36.0.0"
|
||||
ndkPath = "$sdkDirectory/ndk/magisk"
|
||||
ndkVersion = "29.0.13113456"
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 23
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility = JavaVersion.VERSION_21
|
||||
targetCompatibility = JavaVersion.VERSION_21
|
||||
}
|
||||
|
||||
packagingOptions {
|
||||
resources {
|
||||
excludes += arrayOf(
|
||||
"/META-INF/*",
|
||||
"/META-INF/androidx/**",
|
||||
"/META-INF/versions/**",
|
||||
"/org/bouncycastle/**",
|
||||
"/org/apache/commons/**",
|
||||
"/kotlin/**",
|
||||
"/kotlinx/**",
|
||||
"/okhttp3/**",
|
||||
"/*.txt",
|
||||
"/*.bin",
|
||||
"/*.json",
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk7")
|
||||
exclude("org.jetbrains.kotlin", "kotlin-stdlib-jdk8")
|
||||
}
|
||||
|
||||
tasks.withType<KotlinCompile> {
|
||||
compilerOptions {
|
||||
jvmTarget = JvmTarget.JVM_21
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Project.downloadFile(url: String, checksum: String): File {
|
||||
val file = layout.buildDirectory.file(checksum).get().asFile
|
||||
if (file.exists()) {
|
||||
val md = MessageDigest.getInstance("SHA-256")
|
||||
file.inputStream().use { md.update(it.readAllBytes()) }
|
||||
val hash = HexFormat.of().formatHex(md.digest())
|
||||
if (hash != checksum) {
|
||||
file.delete()
|
||||
}
|
||||
}
|
||||
if (!file.exists()) {
|
||||
file.parentFile.mkdirs()
|
||||
URI(url).toURL().openStream().use { dl ->
|
||||
file.outputStream().use {
|
||||
dl.copyTo(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
return file
|
||||
}
|
||||
|
||||
const val BUSYBOX_DOWNLOAD_URL =
|
||||
"https://github.com/topjohnwu/magisk-files/releases/download/files/busybox-1.36.1.1.zip"
|
||||
const val BUSYBOX_ZIP_CHECKSUM =
|
||||
"b4d0551feabaf314e53c79316c980e8f66432e9fb91a69dbbf10a93564b40951"
|
||||
|
||||
fun Project.setupCoreLib() {
|
||||
setupCommon()
|
||||
|
||||
val abiList = Config.abiList
|
||||
|
||||
val syncLibs by tasks.registering(Sync::class) {
|
||||
into("src/main/jniLibs")
|
||||
for (abi in abiList) {
|
||||
into(abi) {
|
||||
from(File(baseDir, "native/out/$abi")) {
|
||||
include("magiskboot", "magiskinit", "magiskpolicy", "magisk", "libinit-ld.so")
|
||||
rename { if (it.endsWith(".so")) it else "lib$it.so" }
|
||||
}
|
||||
}
|
||||
}
|
||||
onlyIf {
|
||||
if (inputs.sourceFiles.files.size != abiList.size * 5)
|
||||
throw StopExecutionException("Please build binaries first! (./build.py binary)")
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
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(File(baseDir, "scripts/update_binary.sh")) {
|
||||
rename { "update-binary" }
|
||||
}
|
||||
from(File(baseDir, "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")
|
||||
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/${this@all.name}/assets")
|
||||
from(File(baseDir, "scripts")) {
|
||||
include("util_functions.sh", "boot_patch.sh", "addon.d.sh",
|
||||
"app_functions.sh", "uninstaller.sh", "module_installer.sh")
|
||||
}
|
||||
from(File(baseDir, "tools/bootctl"))
|
||||
into("chromeos") {
|
||||
from(File(baseDir, "tools/futility"))
|
||||
from(File(baseDir, "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"))
|
||||
}
|
||||
}
|
||||
|
||||
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<String>
|
||||
|
||||
@get:Input
|
||||
abstract val signingConfig: Property<ApkSigningConfig>
|
||||
|
||||
@get:InputFiles
|
||||
abstract val apkFolder: DirectoryProperty
|
||||
|
||||
@get:OutputDirectory
|
||||
abstract val outFolder: DirectoryProperty
|
||||
|
||||
@get:Internal
|
||||
abstract val transformationRequest: Property<ArtifactTransformationRequest<AddCommentTask>>
|
||||
|
||||
@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()
|
||||
|
||||
android {
|
||||
signingConfigs {
|
||||
create("config") {
|
||||
Config["keyStore"]?.also {
|
||||
storeFile = File(baseDir, it)
|
||||
storePassword = Config["keyStorePass"]
|
||||
keyAlias = Config["keyAlias"]
|
||||
keyPassword = Config["keyPass"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
defaultConfig {
|
||||
targetSdk = 36
|
||||
proguardFiles(
|
||||
getDefaultProguardFile("proguard-android-optimize.txt")
|
||||
)
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
signingConfigs["config"].also {
|
||||
debug {
|
||||
signingConfig = if (it.storeFile?.exists() == true) it
|
||||
else signingConfigs["debug"]
|
||||
}
|
||||
release {
|
||||
signingConfig = if (it.storeFile?.exists() == true) it
|
||||
else signingConfigs["debug"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lint {
|
||||
disable += "MissingTranslation"
|
||||
checkReleaseBuilds = false
|
||||
}
|
||||
|
||||
dependenciesInfo {
|
||||
includeInApk = false
|
||||
}
|
||||
|
||||
packaging {
|
||||
jniLibs {
|
||||
useLegacyPackaging = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
val commentTask = tasks.register(
|
||||
"comment${variant.name.replaceFirstChar { it.uppercase() }}",
|
||||
AddCommentTask::class.java
|
||||
)
|
||||
val transformationRequest = variant.artifacts.use(commentTask)
|
||||
.wiredWithDirectories(AddCommentTask::apkFolder, AddCommentTask::outFolder)
|
||||
.toTransformMany(SingleArtifact.APK)
|
||||
val signingConfig = androidApp.buildTypes.getByName(variant.buildType!!).signingConfig
|
||||
commentTask.configure {
|
||||
this.transformationRequest = transformationRequest
|
||||
this.signingConfig = signingConfig
|
||||
this.comment = "version=${Config.version}\n" +
|
||||
"versionCode=${Config.versionCode}\n" +
|
||||
"stubVersion=${Config.stubVersion}\n"
|
||||
this.outFolder.set(layout.buildDirectory.dir("outputs/apk/${variant.name}"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Project.setupMainApk() {
|
||||
setupAppCommon()
|
||||
|
||||
android {
|
||||
namespace = "com.topjohnwu.magisk"
|
||||
|
||||
defaultConfig {
|
||||
applicationId = "com.topjohnwu.magisk"
|
||||
vectorDrawables.useSupportLibrary = true
|
||||
versionName = Config.version
|
||||
versionCode = Config.versionCode
|
||||
ndk {
|
||||
abiFilters += listOf("armeabi-v7a", "arm64-v8a", "x86", "x86_64", "riscv64")
|
||||
debugSymbolLevel = "FULL"
|
||||
}
|
||||
}
|
||||
|
||||
androidComponents.onVariants { variant ->
|
||||
variant.instrumentation.apply {
|
||||
setAsmFramesComputationMode(COMPUTE_FRAMES_FOR_INSTRUMENTED_METHODS)
|
||||
transformClassesWith(
|
||||
DesugarClassVisitorFactory::class.java, InstrumentationScope.ALL) {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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<Delete>("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 =
|
||||
"0ebc6bcb465d1c4b44b7220ab5f0252e6b4eb7fe43da74650476d2798bb29622"
|
||||
|
||||
const val SHAMIKO_DOWNLOAD_URL =
|
||||
"https://github.com/LSPosed/LSPosed.github.io/releases/download/shamiko-383/Shamiko-v1.2.1-383-release.zip"
|
||||
const val SHAMIKO_CHECKSUM =
|
||||
"93754a038c2d8f0e985bad45c7303b96f70a93d8335060e50146f028d3a9b13f"
|
||||
|
||||
fun Project.setupTestApk() {
|
||||
setupAppCommon()
|
||||
|
||||
androidApp.applicationVariants.all {
|
||||
val variantCapped = name.replaceFirstChar { it.uppercase() }
|
||||
val dlTask by tasks.register("download${variantCapped}Lsposed", Sync::class) {
|
||||
from(downloadFile(LSPOSED_DOWNLOAD_URL, LSPOSED_CHECKSUM)) {
|
||||
rename { "lsposed.zip" }
|
||||
}
|
||||
from(downloadFile(SHAMIKO_DOWNLOAD_URL, SHAMIKO_CHECKSUM)) {
|
||||
rename { "shamiko.zip" }
|
||||
}
|
||||
into("src/${this@all.name}/assets")
|
||||
}
|
||||
mergeAssetsProvider.configure { dependsOn(dlTask) }
|
||||
}
|
||||
}
|
||||
@@ -29,7 +29,7 @@ android {
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(project(":app:shared"))
|
||||
api(project(":shared"))
|
||||
|
||||
api(libs.timber)
|
||||
api(libs.markwon.core)
|
||||
|
||||
33
app/gradle.properties
Normal file
33
app/gradle.properties
Normal file
@@ -0,0 +1,33 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
org.gradle.jvmargs=-Xmx2560m -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
org.gradle.parallel=true
|
||||
|
||||
# Enable build cache
|
||||
org.gradle.caching=true
|
||||
|
||||
# Use K2 in kapt
|
||||
kapt.use.k2=true
|
||||
|
||||
# Android
|
||||
android.useAndroidX=true
|
||||
android.injected.testOnly=false
|
||||
android.nonFinalResIds=false
|
||||
|
||||
# Magisk
|
||||
magisk.stubVersion=40
|
||||
magisk.versionCode=28104
|
||||
69
app/gradle/libs.versions.toml
Normal file
69
app/gradle/libs.versions.toml
Normal file
@@ -0,0 +1,69 @@
|
||||
[versions]
|
||||
kotlin = "2.1.20"
|
||||
android = "8.9.2"
|
||||
ksp = "2.1.20-1.0.31"
|
||||
rikka = "1.3.0"
|
||||
navigation = "2.8.9"
|
||||
libsu = "6.0.0"
|
||||
moshi = "1.15.2"
|
||||
okhttp = "4.12.0"
|
||||
retrofit = "2.11.0"
|
||||
room = "2.7.1"
|
||||
|
||||
[libraries]
|
||||
bcpkix = { module = "org.bouncycastle:bcpkix-jdk18on", version = "1.80" }
|
||||
commons-compress = { module = "org.apache.commons:commons-compress", version = "1.27.1" }
|
||||
retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
|
||||
retrofit-moshi = { module = "com.squareup.retrofit2:converter-moshi", version.ref = "retrofit" }
|
||||
retrofit-scalars = { module = "com.squareup.retrofit2:converter-scalars", version.ref = "retrofit" }
|
||||
markwon-core = { module = "io.noties.markwon:core", version = "4.6.2" }
|
||||
okhttp = { module = "com.squareup.okhttp3:okhttp", version.ref = "okhttp" }
|
||||
okhttp-dnsoverhttps = { module = "com.squareup.okhttp3:okhttp-dnsoverhttps", version.ref = "okhttp" }
|
||||
okhttp-logging = { module = "com.squareup.okhttp3:logging-interceptor", version.ref = "okhttp" }
|
||||
moshi = { module = "com.squareup.moshi:moshi", version.ref = "moshi" }
|
||||
moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" }
|
||||
timber = { module = "com.jakewharton.timber:timber", version = "5.0.1" }
|
||||
jgit = { module = "org.eclipse.jgit:org.eclipse.jgit", version = "7.1.0.202411261347-r" }
|
||||
|
||||
# AndroidX
|
||||
activity = { module = "androidx.activity:activity", version = "1.10.1" }
|
||||
appcompat = { module = "androidx.appcompat:appcompat", version = "1.7.0" }
|
||||
core-ktx = { module = "androidx.core:core-ktx", version = "1.16.0" }
|
||||
core-splashscreen = { module = "androidx.core:core-splashscreen", version = "1.0.1" }
|
||||
constraintlayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.2.1" }
|
||||
fragment-ktx = { module = "androidx.fragment:fragment-ktx", version = "1.8.6" }
|
||||
navigation-fragment-ktx = { module = "androidx.navigation:navigation-fragment-ktx", version.ref = "navigation" }
|
||||
navigation-ui-ktx = { module = "androidx.navigation:navigation-ui-ktx", version.ref = "navigation" }
|
||||
profileinstaller = { module = "androidx.profileinstaller:profileinstaller", version = "1.4.1" }
|
||||
recyclerview = { module = "androidx.recyclerview:recyclerview", version = "1.4.0" }
|
||||
room-ktx = { module = "androidx.room:room-ktx", version.ref = "room" }
|
||||
room-runtime = { module = "androidx.room:room-runtime", version.ref = "room" }
|
||||
room-compiler = { module = "androidx.room:room-compiler", version.ref = "room" }
|
||||
swiperefreshlayout = { module = "androidx.swiperefreshlayout:swiperefreshlayout", version = "1.1.0" }
|
||||
transition = { module = "androidx.transition:transition", version = "1.6.0" }
|
||||
collection-ktx = { module = "androidx.collection:collection-ktx", version = "1.5.0" }
|
||||
material = { module = "com.google.android.material:material", version = "1.12.0" }
|
||||
jdk-libs = { module = "com.android.tools:desugar_jdk_libs_nio", version = "2.1.5" }
|
||||
test-runner = { module = "androidx.test:runner", version = "1.6.2" }
|
||||
test-rules = { module = "androidx.test:rules", version = "1.6.1" }
|
||||
test-junit = { module = "androidx.test.ext:junit", version = "1.2.1" }
|
||||
test-uiautomator = { module = "androidx.test.uiautomator:uiautomator", version = "2.3.0" }
|
||||
|
||||
# topjohnwu
|
||||
indeterminate-checkbox = { module = "com.github.topjohnwu:indeterminate-checkbox", version = "1.0.7" }
|
||||
libsu-core = { module = "com.github.topjohnwu.libsu:core", version.ref = "libsu" }
|
||||
libsu-service = { module = "com.github.topjohnwu.libsu:service", version.ref = "libsu" }
|
||||
libsu-nio = { module = "com.github.topjohnwu.libsu:nio", version.ref = "libsu" }
|
||||
|
||||
# Rikka
|
||||
rikka-recyclerview = { module = "dev.rikka.rikkax.recyclerview:recyclerview-ktx", version = "1.3.2" }
|
||||
rikka-layoutinflater = { module = "dev.rikka.rikkax.layoutinflater:layoutinflater", version.ref = "rikka" }
|
||||
rikka-insets = { module = "dev.rikka.rikkax.insets:insets", version.ref = "rikka" }
|
||||
|
||||
# Build plugins
|
||||
android-gradle-plugin = { module = "com.android.tools.build:gradle", version.ref = "android" }
|
||||
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" }
|
||||
lsparanoid-plugin = { module = "org.lsposed.lsparanoid:gradle-plugin", version = "0.6.0" }
|
||||
|
||||
[plugins]
|
||||
BIN
app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
app/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
7
app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
7
app/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@@ -0,0 +1,7 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip
|
||||
networkTimeout=10000
|
||||
validateDistributionUrl=true
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
251
app/gradlew
vendored
Executable file
251
app/gradlew
vendored
Executable file
@@ -0,0 +1,251 @@
|
||||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright © 2015-2021 the original authors.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
#
|
||||
# https://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
#
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
#
|
||||
|
||||
##############################################################################
|
||||
#
|
||||
# Gradle start up script for POSIX generated by Gradle.
|
||||
#
|
||||
# Important for running:
|
||||
#
|
||||
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
|
||||
# noncompliant, but you have some other compliant shell such as ksh or
|
||||
# bash, then to run this script, type that shell name before the whole
|
||||
# command line, like:
|
||||
#
|
||||
# ksh Gradle
|
||||
#
|
||||
# Busybox and similar reduced shells will NOT work, because this script
|
||||
# requires all of these POSIX shell features:
|
||||
# * functions;
|
||||
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
|
||||
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
|
||||
# * compound commands having a testable exit status, especially «case»;
|
||||
# * various built-in commands including «command», «set», and «ulimit».
|
||||
#
|
||||
# Important for patching:
|
||||
#
|
||||
# (2) This script targets any POSIX shell, so it avoids extensions provided
|
||||
# by Bash, Ksh, etc; in particular arrays are avoided.
|
||||
#
|
||||
# The "traditional" practice of packing multiple parameters into a
|
||||
# space-separated string is a well documented source of bugs and security
|
||||
# problems, so this is (mostly) avoided, by progressively accumulating
|
||||
# options in "$@", and eventually passing that to Java.
|
||||
#
|
||||
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
|
||||
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
|
||||
# see the in-line comments for details.
|
||||
#
|
||||
# There are tweaks for specific operating systems such as AIX, CygWin,
|
||||
# Darwin, MinGW, and NonStop.
|
||||
#
|
||||
# (3) This script is generated from the Groovy template
|
||||
# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
|
||||
# within the Gradle project.
|
||||
#
|
||||
# You can find Gradle at https://github.com/gradle/gradle/.
|
||||
#
|
||||
##############################################################################
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
|
||||
# Resolve links: $0 may be a link
|
||||
app_path=$0
|
||||
|
||||
# Need this for daisy-chained symlinks.
|
||||
while
|
||||
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
|
||||
[ -h "$app_path" ]
|
||||
do
|
||||
ls=$( ls -ld "$app_path" )
|
||||
link=${ls#*' -> '}
|
||||
case $link in #(
|
||||
/*) app_path=$link ;; #(
|
||||
*) app_path=$APP_HOME$link ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# This is normally unused
|
||||
# shellcheck disable=SC2034
|
||||
APP_BASE_NAME=${0##*/}
|
||||
# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036)
|
||||
APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s\n' "$PWD" ) || exit
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD=maximum
|
||||
|
||||
warn () {
|
||||
echo "$*"
|
||||
} >&2
|
||||
|
||||
die () {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
} >&2
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
nonstop=false
|
||||
case "$( uname )" in #(
|
||||
CYGWIN* ) cygwin=true ;; #(
|
||||
Darwin* ) darwin=true ;; #(
|
||||
MSYS* | MINGW* ) msys=true ;; #(
|
||||
NONSTOP* ) nonstop=true ;;
|
||||
esac
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD=$JAVA_HOME/jre/sh/java
|
||||
else
|
||||
JAVACMD=$JAVA_HOME/bin/java
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD=java
|
||||
if ! command -v java >/dev/null 2>&1
|
||||
then
|
||||
die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
|
||||
case $MAX_FD in #(
|
||||
max*)
|
||||
# In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
MAX_FD=$( ulimit -H -n ) ||
|
||||
warn "Could not query maximum file descriptor limit"
|
||||
esac
|
||||
case $MAX_FD in #(
|
||||
'' | soft) :;; #(
|
||||
*)
|
||||
# In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked.
|
||||
# shellcheck disable=SC2039,SC3045
|
||||
ulimit -n "$MAX_FD" ||
|
||||
warn "Could not set maximum file descriptor limit to $MAX_FD"
|
||||
esac
|
||||
fi
|
||||
|
||||
# Collect all arguments for the java command, stacking in reverse order:
|
||||
# * args from the command line
|
||||
# * the main class name
|
||||
# * -classpath
|
||||
# * -D...appname settings
|
||||
# * --module-path (only if needed)
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
|
||||
|
||||
# For Cygwin or MSYS, switch paths to Windows format before running java
|
||||
if "$cygwin" || "$msys" ; then
|
||||
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
|
||||
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
|
||||
|
||||
JAVACMD=$( cygpath --unix "$JAVACMD" )
|
||||
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
for arg do
|
||||
if
|
||||
case $arg in #(
|
||||
-*) false ;; # don't mess with options #(
|
||||
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
|
||||
[ -e "$t" ] ;; #(
|
||||
*) false ;;
|
||||
esac
|
||||
then
|
||||
arg=$( cygpath --path --ignore --mixed "$arg" )
|
||||
fi
|
||||
# Roll the args list around exactly as many times as the number of
|
||||
# args, so each arg winds up back in the position where it started, but
|
||||
# possibly modified.
|
||||
#
|
||||
# NB: a `for` loop captures its iteration list before it begins, so
|
||||
# changing the positional parameters here affects neither the number of
|
||||
# iterations, nor the values presented in `arg`.
|
||||
shift # remove old arg
|
||||
set -- "$@" "$arg" # push replacement arg
|
||||
done
|
||||
fi
|
||||
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
|
||||
|
||||
# Collect all arguments for the java command:
|
||||
# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments,
|
||||
# and any embedded shellness will be escaped.
|
||||
# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be
|
||||
# treated as '${Hostname}' itself on the command line.
|
||||
|
||||
set -- \
|
||||
"-Dorg.gradle.appname=$APP_BASE_NAME" \
|
||||
-classpath "$CLASSPATH" \
|
||||
org.gradle.wrapper.GradleWrapperMain \
|
||||
"$@"
|
||||
|
||||
# Stop when "xargs" is not available.
|
||||
if ! command -v xargs >/dev/null 2>&1
|
||||
then
|
||||
die "xargs is not available"
|
||||
fi
|
||||
|
||||
# Use "xargs" to parse quoted args.
|
||||
#
|
||||
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
|
||||
#
|
||||
# In Bash we could simply go:
|
||||
#
|
||||
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
|
||||
# set -- "${ARGS[@]}" "$@"
|
||||
#
|
||||
# but POSIX shell has neither arrays nor command substitution, so instead we
|
||||
# post-process each arg (as a line of input to sed) to backslash-escape any
|
||||
# character that might be a shell metacharacter, then use eval to reverse
|
||||
# that process (while maintaining the separation between arguments), and wrap
|
||||
# the whole thing up as a single "set" statement.
|
||||
#
|
||||
# This will of course break if any of these variables contains a newline or
|
||||
# an unmatched quote.
|
||||
#
|
||||
|
||||
eval "set -- $(
|
||||
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
|
||||
xargs -n1 |
|
||||
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
|
||||
tr '\n' ' '
|
||||
)" '"$@"'
|
||||
|
||||
exec "$JAVACMD" "$@"
|
||||
94
app/gradlew.bat
vendored
Normal file
94
app/gradlew.bat
vendored
Normal file
@@ -0,0 +1,94 @@
|
||||
@rem
|
||||
@rem Copyright 2015 the original author or authors.
|
||||
@rem
|
||||
@rem Licensed under the Apache License, Version 2.0 (the "License");
|
||||
@rem you may not use this file except in compliance with the License.
|
||||
@rem You may obtain a copy of the License at
|
||||
@rem
|
||||
@rem https://www.apache.org/licenses/LICENSE-2.0
|
||||
@rem
|
||||
@rem Unless required by applicable law or agreed to in writing, software
|
||||
@rem distributed under the License is distributed on an "AS IS" BASIS,
|
||||
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
@rem See the License for the specific language governing permissions and
|
||||
@rem limitations under the License.
|
||||
@rem
|
||||
@rem SPDX-License-Identifier: Apache-2.0
|
||||
@rem
|
||||
|
||||
@if "%DEBUG%"=="" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%"=="" set DIRNAME=.
|
||||
@rem This is normally unused
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
|
||||
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if %ERRORLEVEL% equ 0 goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto execute
|
||||
|
||||
echo. 1>&2
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2
|
||||
echo. 1>&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the 1>&2
|
||||
echo location of your Java installation. 1>&2
|
||||
|
||||
goto fail
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if %ERRORLEVEL% equ 0 goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
set EXIT_CODE=%ERRORLEVEL%
|
||||
if %EXIT_CODE% equ 0 set EXIT_CODE=1
|
||||
if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE%
|
||||
exit /b %EXIT_CODE%
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
11
app/settings.gradle.kts
Normal file
11
app/settings.gradle.kts
Normal file
@@ -0,0 +1,11 @@
|
||||
@Suppress("UnstableApiUsage")
|
||||
dependencyResolutionManagement {
|
||||
repositoriesMode = RepositoriesMode.FAIL_ON_PROJECT_REPOS
|
||||
repositories {
|
||||
google()
|
||||
mavenCentral()
|
||||
maven("https://jitpack.io")
|
||||
}
|
||||
}
|
||||
rootProject.name = "Magisk"
|
||||
include(":apk", ":core", ":shared", ":stub", ":test")
|
||||
@@ -41,5 +41,5 @@ android {
|
||||
setupStubApk()
|
||||
|
||||
dependencies {
|
||||
implementation(project(":app:shared"))
|
||||
implementation(project(":shared"))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user