This commit is contained in:
oSumAtrIX 2024-11-29 03:18:57 +01:00
parent 2c65044f3a
commit a5a14ac31a
No known key found for this signature in database
GPG Key ID: A9B3094ACDB604B4
4 changed files with 150 additions and 13 deletions

View File

@ -1,3 +1,7 @@
public final class app/revanced/patches/all/layout/branding/IconPatchKt {
public static final fun getChangeIconPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}
public final class app/revanced/patches/all/misc/activity/exportall/ExportAllActivitiesPatchKt {
public static final fun getExportAllActivitiesPatch ()Lapp/revanced/patcher/patch/ResourcePatch;
}

View File

@ -0,0 +1,137 @@
package app.revanced.patches.all.layout.branding
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.Document
import app.revanced.util.getNode
import java.io.File
import java.io.FilenameFilter
val changeIconPatch = resourcePatch(
name = "Change icon",
description = "Changes the app icon to a custom icon. By default, the \"ReVanced icon\" is used.",
use = false,
) {
val revancedIconOptionValue = "" // Empty value == ReVanced icon.
val pixelDensities = setOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi",
)
val iconOptions = buildMap {
arrayOf("foreground", "background", "monochrome").forEach { iconType ->
this += pixelDensities.associateBy {
stringOption(
key = "${iconType}IconPath",
default = revancedIconOptionValue,
values = mapOf("ReVanced Logo" to revancedIconOptionValue),
title = "Icon file path (Pixel density: $it, Icon type: $iconType)",
description = "The path to the icon file to apply to the app for the pixel density $it " +
"and icon type $iconType.",
)
}
}
// This might confuse the user.
put(
"full",
stringOption(
key = "fullIconPath",
default = revancedIconOptionValue,
values = mapOf("ReVanced Logo" to revancedIconOptionValue),
title = "Full icon file path",
description = "The path to the icon file to apply when the app " +
"does not have a specific icon for the pixel density.",
),
)
}
execute {
manifest {
val applicationNode = getNode("application")
val iconResourceReference = applicationNode.attributes.getNamedItem("android:icon").textContent!!
val iconResourceFiles = resolve(iconResourceReference)
iconResourceFiles.forEach { resourceFile ->
if (resourceFile.extension == "xml" && resourceFile.name.startsWith("ic_launcher")) {
val adaptiveIcon = parseAdaptiveIcon(resourceFile)
// TODO: Replace the background, foreground, and monochrome icons with the custom icons.
} else {
// TODO: Replace the icon with fullIcon.
}
}
}
}
}
context(ResourcePatchContext)
fun <T> manifest(block: Document.() -> T) = document("AndroidManifest.xml").use(block)
context(ResourcePatchContext)
private fun resolve(resourceReference: String): List<File> {
val isMipmap = resourceReference.startsWith("@mipmap/")
val isDrawable = resourceReference.startsWith("@drawable/")
val directories = get("res").listFiles(
if (isMipmap) {
FilenameFilter { _, name -> name.startsWith("mipmap-") }
} else if (isDrawable) {
FilenameFilter { _, name -> name.startsWith("drawable-") }
} else {
throw PatchException("Unsupported resource reference: $resourceReference")
},
)!!
// The name does not have an extension. It is the name of the resource.
val resourceName = resourceReference.split("/").last()
val resources = directories.mapNotNull {
// Find the first file that starts with the resource name.
it.listFiles { _, name -> name.startsWith(resourceName) }!!.firstOrNull()
}
return resources
}
private class IconResource(
val file: File,
val pixelDensity: String,
)
context(ResourcePatchContext)
private fun parseAdaptiveIcon(xmlFile: File) = document(xmlFile.absolutePath).use { adaptiveIconNode ->
val adaptiveIcon = adaptiveIconNode.getNode("adaptive-icon")
fun getIconResourceReference(iconType: String): List<IconResource>? {
val resourceReferenceString = adaptiveIcon.getNode(iconType)?.let {
it.attributes.getNamedItem("android:drawable").textContent!!
}
if (resourceReferenceString == null) {
return null
}
return resolve(resourceReferenceString).map {
IconResource(file = it, pixelDensity = it.parentFile.name.split("-").last())
}
}
AdaptiveIcon(
getIconResourceReference("background")!!,
getIconResourceReference("foreground")!!,
getIconResourceReference("monochrome"),
)
}
private class AdaptiveIcon(
val background: List<IconResource>,
val foreground: List<IconResource>,
val monochrome: List<IconResource>?,
)

View File

@ -1,8 +1,8 @@
package app.revanced.patches.music.layout.compactheader
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.AccessFlags
import app.revanced.patcher.fingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val constructCategoryBarFingerprint = fingerprint {
accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR)
@ -16,7 +16,5 @@ internal val constructCategoryBarFingerprint = fingerprint {
Opcode.IPUT_OBJECT,
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.NEW_INSTANCE,
Opcode.INVOKE_DIRECT,
)
}

View File

@ -23,16 +23,14 @@ fun NodeList.asSequence() = (0 until this.length).asSequence().map { this.item(i
* Returns a sequence for all child nodes.
*/
@Suppress("UNCHECKED_CAST")
fun Node.childElementsSequence() =
this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence<Element>
fun Node.childElementsSequence() = this.childNodes.asSequence().filter { it.nodeType == Node.ELEMENT_NODE } as Sequence<Element>
/**
* Performs the given [action] on each child element.
*/
inline fun Node.forEachChildElement(action: (Element) -> Unit) =
childElementsSequence().forEach {
action(it)
}
inline fun Node.forEachChildElement(action: (Element) -> Unit) = childElementsSequence().forEach {
action(it)
}
/**
* Recursively traverse the DOM tree starting from the given root node.
@ -141,7 +139,8 @@ internal fun Node.addResource(
appendChild(resource.serialize(ownerDocument, resourceCallback))
}
internal fun org.w3c.dom.Document.getNode(tagName: String) = this.getElementsByTagName(tagName).item(0)
internal fun org.w3c.dom.Document.getNode(tagName: String) = getElementsByTagName(tagName).item(0)
internal fun Node.getNode(tagName: String) = childNodes.asSequence().find { it.nodeName == tagName }
internal fun NodeList.findElementByAttributeValue(attributeName: String, value: String): Element? {
for (i in 0 until length) {
@ -164,8 +163,7 @@ internal fun NodeList.findElementByAttributeValue(attributeName: String, value:
return null
}
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) =
findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
internal fun NodeList.findElementByAttributeValueOrThrow(attributeName: String, value: String) = findElementByAttributeValue(attributeName, value) ?: throw PatchException("Could not find: $attributeName $value")
internal fun Element.copyAttributesFrom(oldContainer: Element) {
// Copy attributes from the old element to the new element