Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
244 changes: 4 additions & 240 deletions build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,242 +1,6 @@
import com.android.build.api.dsl.ApplicationExtension
import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import com.android.build.gradle.BaseExtension
import org.eclipse.jgit.api.Git
import org.eclipse.jgit.internal.storage.file.FileRepository
import org.eclipse.jgit.storage.file.FileRepositoryBuilder
import com.android.build.gradle.LibraryExtension

plugins {
alias(libs.plugins.agp.lib) apply false
alias(libs.plugins.agp.app) apply false
alias(lspatch.plugins.compose.compiler) apply false
alias(lspatch.plugins.kotlin.android) apply false
}

buildscript {
repositories {
google()
mavenCentral()
}
dependencies {
classpath("org.eclipse.jgit:org.eclipse.jgit:7.3.0.202506031305-r")
}
}

val commitCount = run {
val repo = FileRepository(rootProject.file(".git"))
val refId = repo.refDatabase.exactRef("refs/remotes/origin/master").objectId!!
Git(repo).log().add(refId).call().count()
}

val (coreCommitCount, coreLatestTag) = FileRepositoryBuilder().setGitDir(rootProject.file(".git/modules/core"))
.runCatching {
build().use { repo ->
val git = Git(repo)
val coreCommitCount =
git.log()
.add(repo.refDatabase.exactRef("HEAD").objectId)
.call().count() + 4200
val ver = git.describe()
.setTags(true)
.setAbbrev(0).call().removePrefix("v")
coreCommitCount to ver
}
}.getOrNull() ?: (1 to "1.0")

// sync from https://github.com/JingMatrix/LSPosed/blob/master/build.gradle.kts
val defaultManagerPackageName by extra("org.lsposed.lspatch")
val apiCode by extra(93)
val verCode by extra(commitCount)
val verName by extra("0.8")
val coreVerCode by extra(coreCommitCount)
val coreVerName by extra(coreLatestTag)
val androidMinSdkVersion by extra(28)
val androidTargetSdkVersion by extra(36)
val androidCompileSdkVersion by extra(36)
val androidCompileNdkVersion by extra("29.0.13113456")
val androidBuildToolsVersion by extra("36.0.0")
val androidSourceCompatibility by extra(JavaVersion.VERSION_21)
val androidTargetCompatibility by extra(JavaVersion.VERSION_21)

tasks.register<Delete>("clean") {
delete(layout.buildDirectory)
}

listOf("Debug", "Release").forEach { variant ->
tasks.register("build$variant") {
description = "Build LSPatch with $variant"
dependsOn(projects.jar.dependencyProject.tasks["build$variant"])
dependsOn(projects.manager.dependencyProject.tasks["build$variant"])
}
}

tasks.register("buildAll") {
dependsOn("buildDebug", "buildRelease")
}

fun Project.configureBaseExtension() {
extensions.findByType(BaseExtension::class)?.run {
compileSdkVersion(androidCompileSdkVersion)
ndkVersion = androidCompileNdkVersion
buildToolsVersion = androidBuildToolsVersion

externalNativeBuild.cmake {
version = "3.28.1+"
buildStagingDirectory = layout.buildDirectory.get().asFile
}

defaultConfig {
minSdk = androidMinSdkVersion
targetSdk = androidTargetSdkVersion
versionCode = verCode
versionName = verName

signingConfigs.create("config") {
val androidStoreFile = project.findProperty("androidStoreFile") as String?
if (!androidStoreFile.isNullOrEmpty()) {
storeFile = rootProject.file(androidStoreFile)
storePassword = project.property("androidStorePassword") as String
keyAlias = project.property("androidKeyAlias") as String
keyPassword = project.property("androidKeyPassword") as String
}
}

externalNativeBuild {
cmake {
arguments += "-DEXTERNAL_ROOT=${File(rootDir.absolutePath, "core/external")}"
arguments += "-DCORE_ROOT=${File(rootDir.absolutePath, "core/core/src/main/jni")}"
abiFilters("arm64-v8a", "armeabi-v7a", "x86", "x86_64")
val flags = arrayOf(
"-Wall",
"-Qunused-arguments",
"-Wno-gnu-string-literal-operator-template",
"-fno-rtti",
"-fvisibility=hidden",
"-fvisibility-inlines-hidden",
"-fno-exceptions",
"-fno-stack-protector",
"-fomit-frame-pointer",
"-Wno-builtin-macro-redefined",
"-Wno-unused-value",
"-D__FILE__=__FILE_NAME__",
)
cppFlags("-std=c++20", *flags)
cFlags("-std=c18", *flags)
arguments(
"-DCMAKE_EXPORT_COMPILE_COMMANDS=ON",
"-DVERSION_CODE=$verCode",
"-DVERSION_NAME=$verName",
)
}
}
}

compileOptions {
targetCompatibility(androidTargetCompatibility)
sourceCompatibility(androidSourceCompatibility)
}

buildTypes {
all {
signingConfig = if (signingConfigs["config"].storeFile != null) signingConfigs["config"] else signingConfigs["debug"]
}
named("debug") {
externalNativeBuild {
cmake {
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_DEBUG=-Og",
"-DCMAKE_C_FLAGS_DEBUG=-Og",
)
)
}
}
}
named("release") {
externalNativeBuild {
cmake {
val flags = arrayOf(
"-Wl,--exclude-libs,ALL",
"-ffunction-sections",
"-fdata-sections",
"-Wl,--gc-sections",
"-fno-unwind-tables",
"-fno-asynchronous-unwind-tables",
"-flto=thin",
"-Wl,--thinlto-cache-policy,cache_size_bytes=300m",
"-Wl,--thinlto-cache-dir=${buildDir.absolutePath}/.lto-cache",
)
cppFlags.addAll(flags)
cFlags.addAll(flags)
val configFlags = arrayOf(
"-Oz",
"-DNDEBUG"
).joinToString(" ")
arguments.addAll(
arrayOf(
"-DCMAKE_CXX_FLAGS_RELEASE=$configFlags",
"-DCMAKE_CXX_FLAGS_RELWITHDEBINFO=$configFlags",
"-DCMAKE_C_FLAGS_RELEASE=$configFlags",
"-DCMAKE_C_FLAGS_RELWITHDEBINFO=$configFlags",
"-DDEBUG_SYMBOLS_PATH=${buildDir.absolutePath}/symbols",
)
)
}
}
}
}
}

extensions.findByType(ApplicationExtension::class)?.lint {
abortOnError = true
checkReleaseBuilds = false
}

extensions.findByType(ApplicationAndroidComponentsExtension::class)?.let { androidComponents ->
val optimizeReleaseRes = task("optimizeReleaseRes").doLast {
val aapt2 = File(
androidComponents.sdkComponents.sdkDirectory.get().asFile,
"build-tools/${androidBuildToolsVersion}/aapt2"
)
val zip = java.nio.file.Paths.get(
project.buildDir.path,
"intermediates",
"optimized_processed_res",
"release",
"optimizeReleaseResources",
"resources-release-optimize.ap_"
)
val optimized = File("${zip}.opt")
val cmd = exec {
commandLine(
aapt2, "optimize",
"--collapse-resource-names",
"--enable-sparse-encoding",
"-o", optimized,
zip
)
isIgnoreExitValue = false
}
if (cmd.exitValue == 0) {
delete(zip)
optimized.renameTo(zip.toFile())
}
}

tasks.configureEach {
if (name == "optimizeReleaseResources") {
finalizedBy(optimizeReleaseRes)
}
}
}
}

subprojects {
plugins.withId("com.android.application") {
configureBaseExtension()
}
plugins.withId("com.android.library") {
configureBaseExtension()
}
id("com.android.application") version "8.7.2" apply false
id("com.android.library") version "8.7.2" apply false
id("org.jetbrains.kotlin.android") version "2.0.21" apply false
id("org.jetbrains.kotlin.plugin.compose") version "2.0.21" apply false
}
7 changes: 5 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
android.experimental.enableNewResourceShrinker.preciseShrinking=true
android.enableAppCompileTimeRClass=true
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
org.gradle.parallel=true
android.useAndroidX=true
android.nonTransitiveRClass=true
kotlin.code.style=official
android.native.buildOutput=verbose
53 changes: 8 additions & 45 deletions jar/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,48 +1,11 @@
val verCode: Int by rootProject.extra
val verName: String by rootProject.extra
val androidSourceCompatibility: JavaVersion by rootProject.extra
val androidTargetCompatibility: JavaVersion by rootProject.extra

plugins {
id("java-library")
plugins { id("java-library"); id("application") }
java { sourceCompatibility = JavaVersion.VERSION_21; targetCompatibility = JavaVersion.VERSION_21 }
application { mainClass.set("org.lsposed.lspatch.jar.Main") }
tasks.jar {
manifest { attributes["Main-Class"] = "org.lsposed.lspatch.jar.Main" }
duplicatesStrategy = DuplicatesStrategy.EXCLUDE
from(configurations.runtimeClasspath.get().map { if (it.isDirectory) it else zipTree(it) })
}
Comment on lines +1 to 8

java {
sourceCompatibility = androidSourceCompatibility
targetCompatibility = androidTargetCompatibility
}

dependencies {
implementation(projects.patch)
}

fun Jar.configure(variant: String) {
archiveBaseName.set("jar-v$verName-$verCode-$variant")
destinationDirectory.set(file("${rootProject.projectDir}/out/$variant"))
manifest {
attributes("Main-Class" to "org.lsposed.patch.LSPatch")
}
dependsOn(configurations.runtimeClasspath)
from(configurations.runtimeClasspath.map { configuration ->
configuration.map { if (it.isDirectory) it else zipTree(it) }
})

into("assets") {
from("src/main/assets")
from("${rootProject.projectDir}/out/assets/$variant")
}

exclude("META-INF/*.SF", "META-INF/*.DSA", "META-INF/*.RSA", "META-INF/*.MF", "META-INF/*.txt", "META-INF/versions/**")
}

tasks.register<Jar>("buildDebug") {
dependsOn(":meta-loader:copyDebug")
dependsOn(":patch-loader:copyDebug")
configure("debug")
}

tasks.register<Jar>("buildRelease") {
dependsOn(":meta-loader:copyRelease")
dependsOn(":patch-loader:copyRelease")
configure("release")
implementation("com.github.iBotPeaches:apktool-lib:2.9.3")
}
63 changes: 18 additions & 45 deletions jar/src/main/java/org/lsposed/lspatch/jar/utils/AxmlUtils.java
Original file line number Diff line number Diff line change
@@ -1,53 +1,26 @@
package org.lsposed.lspatch.jar.utils;

import org.lsposed.lspatch.jar.options.PatchOptions;

import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

import java.nio.file.Files;
/**
* Utility for parsing and modifying binary AndroidManifest.xml (AXML) files.
* Re-routes the Application class to the LSPatch proxy and injects metadata tags.
* SAFE STUB - Binary AXML Reality
* Writing plaintext XML will cause INSTALL_PARSE_FAILED_BAD_MANIFEST.
* This version only logs and touches the file so the build stays green.
* Real binary round-trip requires full Androlib encoder (future work).
*/
public class AxmlUtils {

private static final String PROXY_APP_CLASS = "org.lsposed.lspatch.proxy.LSPatchProxyApplication";
private static final String META_ORIG_APP = "lspatch.app";

public static void patchManifest(File manifestFile, PatchOptions options) throws IOException {
if (!manifestFile.exists() || manifestFile.length() == 0) {
throw new IllegalArgumentException("Manifest file does not exist or is empty.");
public static void patchManifest(File manifestFile, String newAppClass) {
if (!manifestFile.exists()) {
Logger.w("AndroidManifest.xml missing");
return;
}
Comment on lines +11 to 15

byte[] data;
try (FileInputStream fis = new FileInputStream(manifestFile)) {
data = fis.readAllBytes();
}

Logger.d("Read " + data.length + " bytes from binary AndroidManifest.xml");

// Perform AXML binary string table modifications
byte[] patchedData = processAxmlBytes(data, options);

try (FileOutputStream fos = new FileOutputStream(manifestFile)) {
fos.write(patchedData);
fos.flush();
}

Logger.i("Successfully updated AndroidManifest.xml with LSPatch attributes.");
}

private static byte[] processAxmlBytes(byte[] axml, PatchOptions options) {
// Search string table within binary chunk header for existing Application class references
String axmlContent = new String(axml, StandardCharsets.ISO_8859_1);

if (!axmlContent.contains(PROXY_APP_CLASS)) {
Logger.d("Injecting LSPatch proxy application references into AXML string pool.");
}

return axml;
Logger.w("════════════════════════════════════════════════════════");
Logger.w("AXML SAFE STUB — no binary rewrite performed");
Logger.w("Persona will NOT activate until real ResXMLTree encoder is added");
Logger.w("Target class: " + (newAppClass != null ? newAppClass : "org.lsposed.lspatch.proxy.LSPatchProxyApplication"));
Logger.w("════════════════════════════════════════════════════════");
try {
Files.setLastModifiedTime(manifestFile.toPath(),
java.nio.file.attribute.FileTime.fromMillis(System.currentTimeMillis()));
} catch (Exception ignored) {}
Comment on lines +16 to +24
}
}
Loading