feat(wintry): Implement Wintry patcher
This commit introduces a new patcher for WintryXposed. `minSdk` is bumped to 28 to match LSPosed deps
This commit is contained in:
parent
457347167d
commit
5ef7946a9c
11 changed files with 309 additions and 192 deletions
|
|
@ -22,7 +22,7 @@ android {
|
|||
compileSdk = 36
|
||||
|
||||
defaultConfig {
|
||||
minSdk = 24
|
||||
minSdk = 28
|
||||
targetSdk = 36
|
||||
versionCode = 10_00_02
|
||||
versionName = "1.0.2"
|
||||
|
|
@ -169,6 +169,8 @@ dependencies {
|
|||
implementation(libs.bundles.ktor)
|
||||
implementation(libs.bundles.voyager)
|
||||
|
||||
implementation(files("libs/lspatch.aar"))
|
||||
|
||||
implementation(libs.compose.ui.tooling.preview)
|
||||
debugImplementation(libs.compose.ui.tooling)
|
||||
debugImplementation(libs.compose.runtime.tracing)
|
||||
|
|
@ -191,6 +193,28 @@ dependencies {
|
|||
coreLibraryDesugaring(libs.desugaring)
|
||||
}
|
||||
|
||||
configurations.all {
|
||||
// ---- Previous Exclusions ----
|
||||
exclude(group = "org.bouncycastle")
|
||||
exclude(group = "org.checkerframework", module = "checker-qual")
|
||||
exclude(group = "com.aliucord", module = "axml")
|
||||
|
||||
// ---- New Exclusions ----
|
||||
// Exclude apksig, as it's bundled in LSPatch
|
||||
exclude(group = "com.android.tools.build", module = "apksig")
|
||||
|
||||
// Exclude jcommander
|
||||
exclude(group = "com.beust", module = "jcommander")
|
||||
|
||||
// Exclude Google Guava and its related modules
|
||||
exclude(group = "com.google.guava")
|
||||
|
||||
// Exclude other common utility/annotation libraries
|
||||
exclude(group = "com.google.errorprone", module = "error_prone_annotations")
|
||||
exclude(group = "com.google.j2objc", module = "j2objc-annotations")
|
||||
exclude(group = "com.google.code.findbugs", module = "jsr305")
|
||||
}
|
||||
|
||||
fun ProviderFactory.execIgnoreCode(vararg command: String): String = run {
|
||||
val result = exec {
|
||||
commandLine = command.toList()
|
||||
|
|
|
|||
|
|
@ -125,7 +125,7 @@ class PathManager(
|
|||
/**
|
||||
* The APK that is worked on during the patching process.
|
||||
*/
|
||||
fun patchedApk(part: String = "") = patchingWorkingDir().resolve("patched-${part}.apk")
|
||||
fun patchedApk(part: String = "base") = patchingWorkingDir().resolve("patched-$part.apk")
|
||||
|
||||
private companion object {
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
package com.aliucord.manager.patcher
|
||||
|
||||
import com.aliucord.manager.patcher.steps.base.Step
|
||||
import com.aliucord.manager.patcher.steps.download.*
|
||||
import com.aliucord.manager.patcher.steps.install.*
|
||||
import com.aliucord.manager.patcher.steps.patch.*
|
||||
|
|
@ -8,21 +7,23 @@ import com.aliucord.manager.ui.screens.patchopts.PatchOptions
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
class WintryPatchRunner(options: PatchOptions) : StepRunner() {
|
||||
override val steps = persistentListOf<Step>(
|
||||
override val steps = persistentListOf(
|
||||
DownloadDiscordRNABaseStep(),
|
||||
DownloadDiscordRNALangStep(),
|
||||
DownloadDiscordRNAResourcesStep(),
|
||||
DownloadDiscordRNALibStep(),
|
||||
DownloadWintryXposed(),
|
||||
CopyDependenciesStep(),
|
||||
|
||||
PatchIconsStep(options),
|
||||
PatchManifestStep(options),
|
||||
// ReorganizeDexStep(),
|
||||
PatchWintryManifestStep(options),
|
||||
SaveMetadataStep(options),
|
||||
|
||||
AlignmentStep(),
|
||||
SigningStep(),
|
||||
InjectWintryXposedStep(),
|
||||
|
||||
InstallStep(options),
|
||||
// CleanupStep(),
|
||||
CleanupStep(),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -72,9 +72,10 @@ class CopyDependenciesStep : Step(), KoinComponent {
|
|||
}
|
||||
}
|
||||
|
||||
fun copyApkSafely(src: File?, destFile: File, onCopied: (File) -> Unit) {
|
||||
fun copyApkSafely(src: File?, part: String, onCopied: (File) -> Unit) {
|
||||
src?.let {
|
||||
container.log("Copying patched apk from ${it.absolutePath} to ${destFile.absolutePath}")
|
||||
container.log("Copying patched apk from ${it.absolutePath} to $part")
|
||||
val destFile = File(dir, paths.patchedApk(part).name)
|
||||
it.copyTo(destFile)
|
||||
onCopied(destFile)
|
||||
}
|
||||
|
|
@ -85,8 +86,8 @@ class CopyDependenciesStep : Step(), KoinComponent {
|
|||
srcApk.copyTo(patchedApk)
|
||||
|
||||
// Optional APKs
|
||||
copyApkSafely(langApk, File(dir, "lang")) { patchedLangApk = it }
|
||||
copyApkSafely(libApk, File(dir, "lib")) { patchedLibApk = it }
|
||||
copyApkSafely(resApk, File(dir, "res")) { patchedResApk = it }
|
||||
copyApkSafely(langApk, "lang") { patchedLangApk = it }
|
||||
copyApkSafely(libApk, "lib") { patchedLibApk = it }
|
||||
copyApkSafely(resApk, "res") { patchedResApk = it }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
package com.aliucord.manager.patcher.steps.download
|
||||
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.manager.PathManager
|
||||
import com.aliucord.manager.patcher.steps.base.DownloadStep
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class DownloadWintryXposed : DownloadStep() {
|
||||
private val paths: PathManager by inject()
|
||||
|
||||
override val localizedName = R.string.downloading_wintryxposed
|
||||
override val targetFile = paths.patchingDownloadDir.resolve("xposed.apk")
|
||||
override val targetUrl = "https://github.com/wtcord/wt-xposed/releases/download/1002/app-release.apk"
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
package com.aliucord.manager.patcher.steps.install
|
||||
|
||||
import com.aliucord.manager.R
|
||||
import com.aliucord.manager.patcher.StepRunner
|
||||
import com.aliucord.manager.patcher.steps.StepGroup
|
||||
import com.aliucord.manager.patcher.steps.base.Step
|
||||
import com.aliucord.manager.patcher.steps.download.CopyDependenciesStep
|
||||
import com.aliucord.manager.patcher.steps.download.DownloadWintryXposed
|
||||
import com.aliucord.manager.patcher.util.Signer
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.lsposed.lspatch.share.LSPConfig
|
||||
import org.lsposed.patch.LSPatch
|
||||
import org.lsposed.patch.util.Logger
|
||||
import java.io.File
|
||||
|
||||
class InjectWintryXposedStep : Step() {
|
||||
override val group: StepGroup = StepGroup.Install
|
||||
override val localizedName: Int = R.string.injecting_wintryxposed_with_lspatch
|
||||
|
||||
suspend fun patch(
|
||||
container: StepRunner,
|
||||
outputDir: File,
|
||||
apkPaths: List<String>,
|
||||
embeddedModules: List<String>,
|
||||
) {
|
||||
withContext(Dispatchers.IO) {
|
||||
LSPatch(
|
||||
object : Logger() {
|
||||
override fun d(p0: String?) {
|
||||
container.log("[LSPatch:D] $p0")
|
||||
}
|
||||
|
||||
override fun e(p0: String?) {
|
||||
container.log("[LSPatch:E] $p0")
|
||||
}
|
||||
|
||||
override fun i(p0: String?) {
|
||||
container.log("[LSPatch] $p0")
|
||||
}
|
||||
},
|
||||
*apkPaths.toTypedArray(),
|
||||
"-o",
|
||||
outputDir.absolutePath,
|
||||
"-l",
|
||||
"0",
|
||||
"-v",
|
||||
"-m",
|
||||
*embeddedModules.toTypedArray(),
|
||||
"-k",
|
||||
Signer.getKeystoreFile().absolutePath,
|
||||
"password",
|
||||
"alias",
|
||||
"password"
|
||||
).doCommandLine()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
val apks = container.getStep<CopyDependenciesStep>().patchedApks
|
||||
val xposed = container.getStep<DownloadWintryXposed>().targetFile
|
||||
|
||||
container.log("Adding WintryXposed module with LSPatch")
|
||||
container.log("WintryXposed path = ${xposed.absolutePath}")
|
||||
|
||||
// Create temporary folder in working directory
|
||||
val tempDir = apks.first().parentFile!!.resolve("lspatched")
|
||||
|
||||
patch(
|
||||
container,
|
||||
outputDir = tempDir,
|
||||
apkPaths = apks.map { it.absolutePath },
|
||||
embeddedModules = listOf(xposed.absolutePath)
|
||||
)
|
||||
|
||||
// Process each APK and replace with patched version
|
||||
apks.forEach { originalApk ->
|
||||
val baseName = originalApk.nameWithoutExtension
|
||||
// https://github.com/JingMatrix/LSPatch/blob/b98eaf805018c4cc258ded12efe89212a4855e6a/patch/src/main/java/org/lsposed/patch/LSPatch.java#L159-L163
|
||||
// String.format(
|
||||
// Locale.getDefault(), "%s-%d-lspatched.apk",
|
||||
// FilenameUtils.getBaseName(apkFileName),
|
||||
// LSPConfig.instance.VERSION_CODE)
|
||||
// )
|
||||
val patchedApkName = "${baseName}-${LSPConfig.instance.VERSION_CODE}-lspatched.apk"
|
||||
val patchedApk = File(tempDir, patchedApkName)
|
||||
|
||||
if (patchedApk.exists()) {
|
||||
patchedApk.copyTo(originalApk, overwrite = true)
|
||||
container.log("Replaced ${originalApk.name} with ${patchedApk.name}")
|
||||
} else {
|
||||
container.log("Warning: Could not find patched APK for ${originalApk.name}")
|
||||
container.log("Expected patched APK at: ${patchedApk.absolutePath}")
|
||||
}
|
||||
}
|
||||
|
||||
tempDir.deleteRecursively()
|
||||
}
|
||||
}
|
||||
|
|
@ -13,36 +13,32 @@ import com.github.diamondminer88.zip.ZipWriter
|
|||
/**
|
||||
* Patch the APK's AndroidManifest.xml
|
||||
*/
|
||||
class PatchManifestStep(private val options: PatchOptions) : Step() {
|
||||
open class PatchManifestStep(private val options: PatchOptions) : Step() {
|
||||
override val group = StepGroup.Patch
|
||||
override val localizedName = R.string.patch_step_patch_manifests
|
||||
|
||||
protected open fun patchManifest(manifestBytes: ByteArray, isBase: Boolean) = ManifestPatcher.patchManifest(
|
||||
manifestBytes = manifestBytes,
|
||||
packageName = options.packageName,
|
||||
appName = options.appName,
|
||||
debuggable = options.debuggable,
|
||||
)
|
||||
|
||||
override suspend fun execute(container: StepRunner) {
|
||||
val deps = container.getStep<CopyDependenciesStep>()
|
||||
for (apk in deps.patchedApks) {
|
||||
val dependenciesStep = container.getStep<CopyDependenciesStep>()
|
||||
for (apk in dependenciesStep.patchedApks) {
|
||||
container.log("Reading manifest from apk")
|
||||
val manifest = ZipReader(apk)
|
||||
val manifestBytes = ZipReader(apk)
|
||||
.use { zip -> zip.openEntry("AndroidManifest.xml")?.read() }
|
||||
?: throw IllegalArgumentException("No manifest found in APK")
|
||||
|
||||
container.log("Patching manifest")
|
||||
val patchedManifest = if (apk == deps.patchedApk)
|
||||
ManifestPatcher.patchManifest(
|
||||
manifestBytes = manifest,
|
||||
packageName = options.packageName,
|
||||
appName = options.appName,
|
||||
debuggable = options.debuggable,
|
||||
)
|
||||
else {
|
||||
ManifestPatcher.patchManifestPackageName(
|
||||
manifestBytes = manifest,
|
||||
packageName = options.packageName,
|
||||
)
|
||||
}
|
||||
val isBase = apk == dependenciesStep.patchedApk
|
||||
val patchedManifest = patchManifest(manifestBytes, isBase)
|
||||
|
||||
container.log("Writing patched manifest to apk unaligned compressed")
|
||||
ZipWriter(apk, /* append = */ true).use {
|
||||
it.deleteEntry("AndroidManifest.xml", apk == deps.patchedLibApk || apk == deps.patchedApk)
|
||||
it.deleteEntry("AndroidManifest.xml", apk == dependenciesStep.patchedLibApk || apk == dependenciesStep.patchedApk)
|
||||
it.writeEntry("AndroidManifest.xml", patchedManifest)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.aliucord.manager.patcher.steps.patch
|
||||
|
||||
import com.aliucord.manager.patcher.util.ManifestPatcher
|
||||
import com.aliucord.manager.ui.screens.patchopts.PatchOptions
|
||||
|
||||
class PatchWintryManifestStep(val options: PatchOptions) : PatchManifestStep(options) {
|
||||
override fun patchManifest(manifestBytes: ByteArray, isBase: Boolean) = ManifestPatcher.patchManifest(
|
||||
manifestBytes = manifestBytes,
|
||||
packageName = options.packageName,
|
||||
compileSdkVersion = null,
|
||||
compileSdkVersionCodename = null,
|
||||
targetSdkVersion = null,
|
||||
useEmbeddedDex = false,
|
||||
requestLegacyExternalStorage = false,
|
||||
appName = if (isBase) options.appName else null,
|
||||
debuggable = options.debuggable,
|
||||
)
|
||||
}
|
||||
|
|
@ -24,16 +24,26 @@ object ManifestPatcher {
|
|||
|
||||
fun patchManifest(
|
||||
manifestBytes: ByteArray,
|
||||
packageName: String,
|
||||
appName: String,
|
||||
debuggable: Boolean,
|
||||
packageName: String? = null,
|
||||
appName: String? = null,
|
||||
debuggable: Boolean? = null,
|
||||
compileSdkVersion: Int? = 23,
|
||||
compileSdkVersionCodename: String? = "6.0-2438415",
|
||||
addManageExternalStorage: Boolean? = true,
|
||||
targetSdkVersion: Int? = if (Build.VERSION.SDK_INT >= 31) 30 else 28,
|
||||
requestLegacyExternalStorage: Boolean? = true,
|
||||
vmSafeMode: Boolean? = true,
|
||||
useEmbeddedDex: Boolean? = true,
|
||||
extractNativeLibs: Boolean? = false,
|
||||
addAliucordMetadata: Boolean? = true,
|
||||
modifyPermissions: Boolean? = true,
|
||||
modifyProviders: Boolean? = true,
|
||||
modifyActivities: Boolean? = true,
|
||||
): ByteArray {
|
||||
val reader = AxmlReader(manifestBytes)
|
||||
val writer = AxmlWriter()
|
||||
|
||||
reader.accept(object : AxmlVisitor(writer) {
|
||||
// Without this, decompiling the finished manifest has the android namespace
|
||||
// under an autogenerated name like axml_00 or something.
|
||||
override fun ns(prefix: String?, uri: String?, ln: Int) {
|
||||
val realUri = uri ?: ANDROID_NAMESPACE
|
||||
super.ns(prefix, realUri, ln)
|
||||
|
|
@ -42,94 +52,98 @@ object ManifestPatcher {
|
|||
override fun child(ns: String?, name: String?) =
|
||||
object : ReplaceAttrsVisitor(
|
||||
super.child(ns, name),
|
||||
mapOf(
|
||||
PACKAGE to packageName,
|
||||
COMPILE_SDK_VERSION to 23,
|
||||
COMPILE_SDK_VERSION_CODENAME to "6.0-2438415"
|
||||
)
|
||||
buildMap {
|
||||
packageName?.let { put(PACKAGE, it) }
|
||||
compileSdkVersion?.let { put(COMPILE_SDK_VERSION, it) }
|
||||
compileSdkVersionCodename?.let { put(COMPILE_SDK_VERSION_CODENAME, it) }
|
||||
}
|
||||
) {
|
||||
private var addExternalStoragePerm = false
|
||||
private var shouldAddExternalStoragePerm = false
|
||||
|
||||
override fun child(ns: String?, name: String): NodeVisitor {
|
||||
val nv = super.child(ns, name)
|
||||
|
||||
// Add MANAGE_EXTERNAL_STORAGE when necessary
|
||||
if (addExternalStoragePerm) {
|
||||
if (shouldAddExternalStoragePerm && addManageExternalStorage == true) {
|
||||
super
|
||||
.child(null, "uses-permission")
|
||||
.attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, Manifest.permission.MANAGE_EXTERNAL_STORAGE)
|
||||
addExternalStoragePerm = false
|
||||
shouldAddExternalStoragePerm = false
|
||||
}
|
||||
|
||||
return when (name) {
|
||||
"uses-permission" -> object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name != "maxSdkVersion") {
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
}
|
||||
|
||||
// Set the add external storage permission to be added after WRITE_EXTERNAL_STORAGE (which is after read)
|
||||
if (name == "name" && value == Manifest.permission.READ_EXTERNAL_STORAGE) {
|
||||
addExternalStoragePerm = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"uses-sdk" -> object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name == "targetSdkVersion") {
|
||||
val version = if (Build.VERSION.SDK_INT >= 31) 30 else 28
|
||||
super.attr(ns, name, resourceId, type, version)
|
||||
} else {
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
"permission" -> object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
when (name) {
|
||||
"name" -> (value as String).replace("com.discord", packageName)
|
||||
else -> value
|
||||
"uses-permission" -> if (modifyPermissions != false) {
|
||||
object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name != "maxSdkVersion") {
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
}
|
||||
)
|
||||
|
||||
// Set the add external storage permission to be added after WRITE_EXTERNAL_STORAGE (which is after read)
|
||||
if (name == "name" && value == Manifest.permission.READ_EXTERNAL_STORAGE) {
|
||||
shouldAddExternalStoragePerm = true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else nv
|
||||
|
||||
"uses-sdk" -> if (targetSdkVersion != null) {
|
||||
object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String?, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name == "targetSdkVersion") {
|
||||
super.attr(ns, name, resourceId, type, targetSdkVersion)
|
||||
} else {
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else nv
|
||||
|
||||
"permission" -> if (modifyPermissions != false && packageName != null) {
|
||||
object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
when (name) {
|
||||
"name" -> (value as String).replace("com.discord", packageName)
|
||||
else -> value
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
} else nv
|
||||
|
||||
"application" -> object : ReplaceAttrsVisitor(
|
||||
nv,
|
||||
mapOf(
|
||||
LABEL to appName,
|
||||
DEBUGGABLE to debuggable,
|
||||
REQUEST_LEGACY_EXTERNAL_STORAGE to true,
|
||||
VM_SAFE_MODE to true,
|
||||
USE_EMBEDDED_DEX to true,
|
||||
EXTRACT_NATIVE_LIBS to false,
|
||||
)
|
||||
buildMap {
|
||||
appName?.let { put(LABEL, it) }
|
||||
debuggable?.let { put(DEBUGGABLE, it) }
|
||||
requestLegacyExternalStorage?.let { put(REQUEST_LEGACY_EXTERNAL_STORAGE, it) }
|
||||
vmSafeMode?.let { put(VM_SAFE_MODE, it) }
|
||||
useEmbeddedDex?.let { put(USE_EMBEDDED_DEX, it) }
|
||||
extractNativeLibs?.let { put(EXTRACT_NATIVE_LIBS, !it) } // Inverted since we set to false originally
|
||||
}
|
||||
) {
|
||||
private var addDebuggable = debuggable
|
||||
private var addLegacyStorage = true
|
||||
private var addUseEmbeddedDex = true
|
||||
private var addExtractNativeLibs = true
|
||||
private var addMetadata = true
|
||||
private var shouldAddDebuggable = debuggable ?: false
|
||||
private var shouldAddLegacyStorage = requestLegacyExternalStorage ?: false
|
||||
private var shouldAddUseEmbeddedDex = useEmbeddedDex ?: false
|
||||
private var shouldAddExtractNativeLibs = extractNativeLibs ?: false
|
||||
private var shouldAddMetadata = addAliucordMetadata ?: false
|
||||
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
if (name == NETWORK_SECURITY_CONFIG) return
|
||||
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) addLegacyStorage = false
|
||||
if (name == USE_EMBEDDED_DEX) addUseEmbeddedDex = false
|
||||
if (name == EXTRACT_NATIVE_LIBS) addExtractNativeLibs = false
|
||||
if (name == DEBUGGABLE) addDebuggable = false
|
||||
if (name == REQUEST_LEGACY_EXTERNAL_STORAGE) shouldAddLegacyStorage = false
|
||||
if (name == USE_EMBEDDED_DEX) shouldAddUseEmbeddedDex = false
|
||||
if (name == EXTRACT_NATIVE_LIBS) shouldAddExtractNativeLibs = false
|
||||
if (name == DEBUGGABLE) shouldAddDebuggable = false
|
||||
super.attr(ns, name, resourceId, type, value)
|
||||
}
|
||||
|
||||
override fun child(ns: String?, name: String): NodeVisitor {
|
||||
val visitor = super.child(ns, name)
|
||||
|
||||
// Adds a <meta-data> tag to make multi-install detection by HomeScreen work
|
||||
if (addMetadata) {
|
||||
addMetadata = false
|
||||
if (shouldAddMetadata && addAliucordMetadata == true) {
|
||||
shouldAddMetadata = false
|
||||
super.child(ANDROID_NAMESPACE, "meta-data").apply {
|
||||
attr(ANDROID_NAMESPACE, "name", android.R.attr.name, TYPE_STRING, "isAliucord")
|
||||
attr(ANDROID_NAMESPACE, "value", android.R.attr.value, TYPE_INT_BOOLEAN, 1)
|
||||
|
|
@ -137,46 +151,57 @@ object ManifestPatcher {
|
|||
}
|
||||
|
||||
return when (name) {
|
||||
"activity" -> ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
|
||||
"provider" -> object : NodeVisitor(visitor) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
if (name == "authorities") {
|
||||
(value as String).replace("com.discord", packageName)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
)
|
||||
"activity" -> if (modifyActivities != false && appName != null) {
|
||||
ReplaceAttrsVisitor(visitor, mapOf("label" to appName))
|
||||
} else visitor
|
||||
|
||||
"provider" -> if (modifyProviders != false && packageName != null) {
|
||||
object : NodeVisitor(visitor) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
if (name == "authorities") {
|
||||
(value as String).replace("com.discord", packageName)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
} else visitor
|
||||
|
||||
else -> visitor
|
||||
}
|
||||
}
|
||||
|
||||
override fun end() {
|
||||
if (addLegacyStorage && Build.VERSION.SDK_INT >= 29) super.attr(
|
||||
ANDROID_NAMESPACE,
|
||||
REQUEST_LEGACY_EXTERNAL_STORAGE,
|
||||
android.R.attr.requestLegacyExternalStorage,
|
||||
TYPE_INT_BOOLEAN,
|
||||
1
|
||||
)
|
||||
if (addDebuggable) super.attr(ANDROID_NAMESPACE, DEBUGGABLE, android.R.attr.debuggable, TYPE_INT_BOOLEAN, 1)
|
||||
if (shouldAddLegacyStorage && Build.VERSION.SDK_INT >= 29 && requestLegacyExternalStorage == true) {
|
||||
super.attr(
|
||||
ANDROID_NAMESPACE,
|
||||
REQUEST_LEGACY_EXTERNAL_STORAGE,
|
||||
android.R.attr.requestLegacyExternalStorage,
|
||||
TYPE_INT_BOOLEAN,
|
||||
1
|
||||
)
|
||||
}
|
||||
if (shouldAddDebuggable && debuggable == true) {
|
||||
super.attr(ANDROID_NAMESPACE, DEBUGGABLE, android.R.attr.debuggable, TYPE_INT_BOOLEAN, 1)
|
||||
}
|
||||
|
||||
// Disable AOT (Necessary for AOSP Android 15)
|
||||
if (Build.VERSION.SDK_INT >= 29 && addUseEmbeddedDex) {
|
||||
if (Build.VERSION.SDK_INT >= 29 && shouldAddUseEmbeddedDex && useEmbeddedDex == true) {
|
||||
super.attr(ANDROID_NAMESPACE, USE_EMBEDDED_DEX, android.R.attr.useEmbeddedDex, TYPE_INT_BOOLEAN, 1)
|
||||
}
|
||||
|
||||
if (addExtractNativeLibs) super.attr(
|
||||
ANDROID_NAMESPACE,
|
||||
EXTRACT_NATIVE_LIBS,
|
||||
android.R.attr.extractNativeLibs,
|
||||
TYPE_INT_BOOLEAN,
|
||||
0
|
||||
)
|
||||
if (shouldAddExtractNativeLibs && extractNativeLibs == false) {
|
||||
super.attr(
|
||||
ANDROID_NAMESPACE,
|
||||
EXTRACT_NATIVE_LIBS,
|
||||
android.R.attr.extractNativeLibs,
|
||||
TYPE_INT_BOOLEAN,
|
||||
0
|
||||
)
|
||||
}
|
||||
|
||||
super.end()
|
||||
}
|
||||
|
|
@ -191,72 +216,6 @@ object ManifestPatcher {
|
|||
return writer.toByteArray()
|
||||
}
|
||||
|
||||
fun patchManifestPackageName(
|
||||
manifestBytes: ByteArray,
|
||||
packageName: String,
|
||||
): ByteArray {
|
||||
val reader = AxmlReader(manifestBytes)
|
||||
val writer = AxmlWriter()
|
||||
|
||||
reader.accept(object : AxmlVisitor(writer) {
|
||||
override fun ns(prefix: String?, uri: String?, ln: Int) {
|
||||
val realUri = uri ?: ANDROID_NAMESPACE
|
||||
super.ns(prefix, realUri, ln)
|
||||
}
|
||||
|
||||
override fun child(ns: String?, name: String?) =
|
||||
object : ReplaceAttrsVisitor(
|
||||
super.child(ns, name),
|
||||
mapOf(PACKAGE to packageName)
|
||||
) {
|
||||
override fun child(ns: String?, name: String): NodeVisitor {
|
||||
val nv = super.child(ns, name)
|
||||
|
||||
return when (name) {
|
||||
"permission" -> object : NodeVisitor(nv) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
if (name == "name") {
|
||||
(value as String).replace("com.discord", packageName)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
"application" -> object : NodeVisitor(nv) {
|
||||
override fun child(ns: String?, name: String): NodeVisitor {
|
||||
val visitor = super.child(ns, name)
|
||||
return when (name) {
|
||||
"provider" -> object : NodeVisitor(visitor) {
|
||||
override fun attr(ns: String?, name: String, resourceId: Int, type: Int, value: Any?) {
|
||||
super.attr(
|
||||
ns, name, resourceId, type,
|
||||
if (name == "authorities") {
|
||||
(value as String).replace("com.discord", packageName)
|
||||
} else {
|
||||
value
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> visitor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
else -> nv
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
return writer.toByteArray()
|
||||
}
|
||||
|
||||
private open class ReplaceAttrsVisitor(
|
||||
nv: NodeVisitor,
|
||||
private val attrs: Map<String, Any>,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ package com.aliucord.manager.patcher.util
|
|||
|
||||
import com.aliucord.manager.manager.PathManager
|
||||
import com.android.apksig.ApkSigner
|
||||
import com.android.apksig.KeyConfig
|
||||
import org.bouncycastle.asn1.x500.X500Name
|
||||
import org.bouncycastle.asn1.x509.SubjectPublicKeyInfo
|
||||
import org.bouncycastle.cert.X509v3CertificateBuilder
|
||||
|
|
@ -44,7 +43,7 @@ object Signer : KoinComponent {
|
|||
|
||||
ApkSigner.SignerConfig.Builder(
|
||||
"Aliucord Manager signer",
|
||||
KeyConfig.Jca(keyStore.getKey(alias, password) as PrivateKey),
|
||||
keyStore.getKey(alias, password) as PrivateKey,
|
||||
listOf(certificate)
|
||||
).build()
|
||||
}
|
||||
|
|
@ -99,5 +98,9 @@ object Signer : KoinComponent {
|
|||
tmpApk.renameTo(apkFile)
|
||||
}
|
||||
|
||||
fun getKeystoreFile(): File {
|
||||
return paths.keystoreFile
|
||||
}
|
||||
|
||||
private class KeySet(val publicKey: X509Certificate, val privateKey: PrivateKey)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -250,4 +250,6 @@
|
|||
<string name="downloading_base_apk">Downloading base APK</string>
|
||||
<string name="downloading_libs_apk">Downloading libs apk</string>
|
||||
<string name="downloading_resources_apk">Downloading resources apk</string>
|
||||
<string name="injecting_wintryxposed_with_lspatch">Injecting WintryXposed with LSPatch</string>
|
||||
<string name="downloading_wintryxposed">Downloading WintryXposed</string>
|
||||
</resources>
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue