build: Bump dependencies

This commit also migrates away from deprecated to new APIs
This commit is contained in:
oSumAtrIX 2024-02-13 03:29:21 +01:00
parent 2c438e414d
commit bdc54ef318
38 changed files with 464 additions and 456 deletions

View file

@ -1736,6 +1736,7 @@ public final class app/revanced/util/ResourceUtilsKt {
public static final fun asSequence (Lorg/w3c/dom/NodeList;)Lkotlin/sequences/Sequence; public static final fun asSequence (Lorg/w3c/dom/NodeList;)Lkotlin/sequences/Sequence;
public static final fun childElementsSequence (Lorg/w3c/dom/Node;)Lkotlin/sequences/Sequence; public static final fun childElementsSequence (Lorg/w3c/dom/Node;)Lkotlin/sequences/Sequence;
public static final fun copyResources (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;)V public static final fun copyResources (Lapp/revanced/patcher/data/ResourceContext;Ljava/lang/String;[Lapp/revanced/util/ResourceGroup;)V
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/Document;Lapp/revanced/patcher/util/Document;)Ljava/lang/AutoCloseable;
public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable; public static final fun copyXmlNode (Ljava/lang/String;Lapp/revanced/patcher/util/DomFileEditor;Lapp/revanced/patcher/util/DomFileEditor;)Ljava/lang/AutoCloseable;
public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V public static final fun doRecursively (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V
public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V public static final fun forEachChildElement (Lorg/w3c/dom/Node;Lkotlin/jvm/functions/Function1;)V

View file

@ -7,29 +7,32 @@ import app.revanced.patcher.patch.annotation.Patch
@Patch( @Patch(
name = "Export all activities", name = "Export all activities",
description = "Makes all app activities exportable.", description = "Makes all app activities exportable.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object ExportAllActivitiesPatch : ResourcePatch() { object ExportAllActivitiesPatch : ResourcePatch() {
private const val EXPORTED_FLAG = "android:exported" private const val EXPORTED_FLAG = "android:exported"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.document["AndroidManifest.xml"].use { document ->
val document = editor.file
val activities = document.getElementsByTagName("activity") val activities = document.getElementsByTagName("activity")
for(i in 0..activities.length) { for (i in 0..activities.length) {
activities.item(i)?.apply { activities.item(i)?.apply {
val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG) val exportedAttribute = attributes.getNamedItem(EXPORTED_FLAG)
if (exportedAttribute != null) { if (exportedAttribute != null) {
if (exportedAttribute.nodeValue != "true") if (exportedAttribute.nodeValue != "true") {
exportedAttribute.nodeValue = "true" exportedAttribute.nodeValue = "true"
}
} }
// Reason why the attribute is added in the case it does not exist: // Reason why the attribute is added in the case it does not exist:
// https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604 // https://github.com/revanced/revanced-patches/pull/1751/files#r1141481604
else document.createAttribute(EXPORTED_FLAG) else {
.apply { value = "true" } document.createAttribute(EXPORTED_FLAG)
.let(attributes::setNamedItem) .apply { value = "true" }
.let(attributes::setNamedItem)
}
} }
} }
} }

View file

@ -7,16 +7,14 @@ import app.revanced.patcher.patch.annotation.Patch
@Patch( @Patch(
name = "Predictive back gesture", name = "Predictive back gesture",
description = "Enables the predictive back gesture introduced on Android 13.", description = "Enables the predictive back gesture introduced on Android 13.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object PredictiveBackGesturePatch : ResourcePatch() { object PredictiveBackGesturePatch : ResourcePatch() {
private const val FLAG = "android:enableOnBackInvokedCallback" private const val FLAG = "android:enableOnBackInvokedCallback"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.document["AndroidManifest.xml"].use { document ->
val document = editor.file
with(document.getElementsByTagName("application").item(0)) { with(document.getElementsByTagName("application").item(0)) {
if (attributes.getNamedItem(FLAG) != null) return@with if (attributes.getNamedItem(FLAG) != null) return@with

View file

@ -8,16 +8,16 @@ import org.w3c.dom.Element
@Patch( @Patch(
name = "Enable Android debugging", name = "Enable Android debugging",
description = "Enables Android debugging capabilities. This can slow down the app.", description = "Enables Android debugging capabilities. This can slow down the app.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object EnableAndroidDebuggingPatch : ResourcePatch() { object EnableAndroidDebuggingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { dom -> context.document["AndroidManifest.xml"].use { document ->
val applicationNode = dom val applicationNode =
.file document
.getElementsByTagName("application") .getElementsByTagName("application")
.item(0) as Element .item(0) as Element
// set application as debuggable // set application as debuggable
applicationNode.setAttribute("android:debuggable", "true") applicationNode.setAttribute("android:debuggable", "true")

View file

@ -11,16 +11,15 @@ import java.io.File
name = "Override certificate pinning", name = "Override certificate pinning",
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.", description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
dependencies = [EnableAndroidDebuggingPatch::class], dependencies = [EnableAndroidDebuggingPatch::class],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object OverrideCertificatePinningPatch : ResourcePatch() { object OverrideCertificatePinningPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
val resXmlDirectory = context["res/xml"] val resXmlDirectory = context.get("res/xml", false)
// Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist. // Add android:networkSecurityConfig="@xml/network_security_config" and the "networkSecurityConfig" attribute if it does not exist.
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.document["AndroidManifest.xml"].use { document ->
val document = editor.file
val applicationNode = document.getElementsByTagName("application").item(0) as Element val applicationNode = document.getElementsByTagName("application").item(0) as Element
if (!applicationNode.hasAttribute("networkSecurityConfig")) { if (!applicationNode.hasAttribute("networkSecurityConfig")) {
@ -54,7 +53,7 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
</trust-anchors> </trust-anchors>
</debug-overrides> </debug-overrides>
</network-security-config> </network-security-config>
""" """,
) )
} else { } else {
// If the file already exists. // If the file already exists.
@ -63,12 +62,11 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
writeText( writeText(
text.replace( text.replace(
"<trust-anchors>", "<trust-anchors>",
"<trust-anchors>\n<certificates src=\"user\" overridePins=\"true\" />\n<certificates src=\"system\" />" "<trust-anchors>\n<certificates src=\"user\" overridePins=\"true\" />\n<certificates src=\"system\" />",
) ),
) )
} }
} }
} }
} }
} }

View file

@ -11,20 +11,21 @@ import java.io.Closeable
@Patch( @Patch(
name = "Change package name", name = "Change package name",
description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.", description = "Appends \".revanced\" to the package name by default. Changing the package name of the app can lead to unexpected issues.",
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object ChangePackageNamePatch : ResourcePatch(), Closeable { object ChangePackageNamePatch : ResourcePatch(), Closeable {
private val packageNameOption = stringPatchOption( private val packageNameOption =
key = "packageName", stringPatchOption(
default = "Default", key = "packageName",
values = mapOf("Default" to "Default"), default = "Default",
title = "Package name", values = mapOf("Default" to "Default"),
description = "The name of the package to rename the app to.", title = "Package name",
required = true description = "The name of the package to rename the app to.",
) { required = true,
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) ) {
} it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
}
private lateinit var context: ResourceContext private lateinit var context: ResourceContext
@ -43,20 +44,25 @@ object ChangePackageNamePatch : ResourcePatch(), Closeable {
fun setOrGetFallbackPackageName(fallbackPackageName: String): String { fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
val packageName = packageNameOption.value!! val packageName = packageNameOption.value!!
return if (packageName == packageNameOption.default) return if (packageName == packageNameOption.default) {
fallbackPackageName.also { packageNameOption.value = it } fallbackPackageName.also { packageNameOption.value = it }
else } else {
packageName packageName
}
} }
override fun close() = context.xmlEditor["AndroidManifest.xml"].use { editor -> override fun close() =
val replacementPackageName = packageNameOption.value context.document["AndroidManifest.xml"].use { document ->
val replacementPackageName = packageNameOption.value
val manifest = editor.file.getElementsByTagName("manifest").item(0) as Element val manifest = document.getElementsByTagName("manifest").item(0) as Element
manifest.setAttribute( manifest.setAttribute(
"package", "package",
if (replacementPackageName != packageNameOption.default) replacementPackageName if (replacementPackageName != packageNameOption.default) {
else "${manifest.getAttribute("package")}.revanced" replacementPackageName
) } else {
} "${manifest.getAttribute("package")}.revanced"
},
)
}
} }

View file

@ -5,7 +5,7 @@ import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.PatchException import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor import app.revanced.patcher.util.Document
import app.revanced.patches.all.misc.resources.AddResourcesPatch.resources import app.revanced.patches.all.misc.resources.AddResourcesPatch.resources
import app.revanced.util.* import app.revanced.util.*
import app.revanced.util.resource.ArrayResource import app.revanced.util.resource.ArrayResource
@ -19,6 +19,7 @@ import java.util.*
* An identifier of an app. For example, `youtube`. * An identifier of an app. For example, `youtube`.
*/ */
private typealias AppId = String private typealias AppId = String
/** /**
* An identifier of a patch. For example, `ad.general.HideAdsPatch`. * An identifier of a patch. For example, `ad.general.HideAdsPatch`.
*/ */
@ -28,10 +29,12 @@ private typealias PatchId = String
* A set of resources of a patch. * A set of resources of a patch.
*/ */
private typealias PatchResources = MutableSet<BaseResource> private typealias PatchResources = MutableSet<BaseResource>
/** /**
* A map of resources belonging to a patch. * A map of resources belonging to a patch.
*/ */
private typealias AppResources = MutableMap<PatchId, PatchResources> private typealias AppResources = MutableMap<PatchId, PatchResources>
/** /**
* A map of resources belonging to an app. * A map of resources belonging to an app.
*/ */
@ -67,40 +70,42 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
this.context = context this.context = context
resources = buildMap { resources =
/** buildMap {
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map. /**
* * Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
* @param value The value of the resource. For example, `values` or `values-de`. *
* @param resourceKind The kind of the resource. For example, `strings` or `arrays`. * @param value The value of the resource. For example, `values` or `values-de`.
* @param transform A function that transforms the [Node]s from the XML files to a [BaseResource]. * @param resourceKind The kind of the resource. For example, `strings` or `arrays`.
*/ * @param transform A function that transforms the [Node]s from the XML files to a [BaseResource].
fun addResources( */
value: Value, fun addResources(
resourceKind: String, value: Value,
transform: (Node) -> BaseResource, resourceKind: String,
) { transform: (Node) -> BaseResource,
inputStreamFromBundledResource( ) {
"addresources", inputStreamFromBundledResource(
"$value/$resourceKind.xml" "addresources",
)?.let { stream -> "$value/$resourceKind.xml",
// Add the resources associated with the given value to the map, )?.let { stream ->
// instead of overwriting it. // Add the resources associated with the given value to the map,
// This covers the example case such as adding strings and arrays of the same value. // instead of overwriting it.
getOrPut(value, ::mutableMapOf).apply { // This covers the example case such as adding strings and arrays of the same value.
context.xmlEditor[stream].use { getOrPut(value, ::mutableMapOf).apply {
it.file.getElementsByTagName("app").asSequence().forEach { app -> context.document[stream].use {
val appId = app.attributes.getNamedItem("id").textContent it.getElementsByTagName("app").asSequence().forEach { app ->
val appId = app.attributes.getNamedItem("id").textContent
getOrPut(appId, ::mutableMapOf).apply { getOrPut(appId, ::mutableMapOf).apply {
app.forEachChildElement { patch -> app.forEachChildElement { patch ->
val patchId = patch.attributes.getNamedItem("id").textContent val patchId = patch.attributes.getNamedItem("id").textContent
getOrPut(patchId, ::mutableSetOf).apply { getOrPut(patchId, ::mutableSetOf).apply {
patch.forEachChildElement { resourceNode -> patch.forEachChildElement { resourceNode ->
val resource = transform(resourceNode) val resource = transform(resourceNode)
add(resource) add(resource)
}
} }
} }
} }
@ -109,23 +114,22 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
} }
} }
} }
}
// Stage all resources to a temporary map. // Stage all resources to a temporary map.
// Staged resources consumed by AddResourcesPatch#invoke(PatchClass) // Staged resources consumed by AddResourcesPatch#invoke(PatchClass)
// are later used in AddResourcesPatch#close. // are later used in AddResourcesPatch#close.
try { try {
val addStringResources = { value: Value -> val addStringResources = { value: Value ->
addResources(value, "strings", StringResource::fromNode) addResources(value, "strings", StringResource::fromNode)
}
Locale.getISOLanguages().asSequence().map { "values-$it" }.forEach { addStringResources(it) }
addStringResources("values")
addResources("values", "arrays", ArrayResource::fromNode)
} catch (e: Exception) {
throw PatchException("Failed to read resources", e)
} }
Locale.getISOLanguages().asSequence().map { "values-$it" }.forEach { addStringResources(it) }
addStringResources("values")
addResources("values", "arrays", ArrayResource::fromNode)
} catch (e: Exception) {
throw PatchException("Failed to read resources", e)
} }
}
} }
/** /**
@ -136,8 +140,10 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* *
* @return True if the resource was added, false if it already existed. * @return True if the resource was added, false if it already existed.
*/ */
operator fun invoke(value: Value, resource: BaseResource) = operator fun invoke(
getOrPut(value, ::mutableSetOf).add(resource) value: Value,
resource: BaseResource,
) = getOrPut(value, ::mutableSetOf).add(resource)
/** /**
* Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut]. * Adds a list of [BaseResource]s to the map using [MutableMap.getOrPut].
@ -147,8 +153,10 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* *
* @return True if the resources were added, false if they already existed. * @return True if the resources were added, false if they already existed.
*/ */
operator fun invoke(value: Value, resources: Iterable<BaseResource>) = operator fun invoke(
getOrPut(value, ::mutableSetOf).addAll(resources) value: Value,
resources: Iterable<BaseResource>,
) = getOrPut(value, ::mutableSetOf).addAll(resources)
/** /**
* Adds a [StringResource]. * Adds a [StringResource].
@ -177,10 +185,9 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
*/ */
operator fun invoke( operator fun invoke(
name: String, name: String,
items: List<String> items: List<String>,
) = invoke("values", ArrayResource(name, items)) ) = invoke("values", ArrayResource(name, items))
/** /**
* Puts all resources of any [Value] staged in [resources] for the given [PatchClass] to [AddResourcesPatch]. * Puts all resources of any [Value] staged in [resources] for the given [PatchClass] to [AddResourcesPatch].
* *
@ -209,7 +216,7 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
appId to patchId appId to patchId
} }
} },
): Boolean { ): Boolean {
val (appId, patchId) = patch.parseIds() val (appId, patchId) = patch.parseIds()
@ -218,7 +225,7 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
// Stage resources for the given patch to AddResourcesPatch associated with their value. // Stage resources for the given patch to AddResourcesPatch associated with their value.
resources.forEach { (value, resources) -> resources.forEach { (value, resources) ->
resources[appId]?.get(patchId)?.let { patchResources -> resources[appId]?.get(patchId)?.let { patchResources ->
if (invoke(value, patchResources)) result = true if (invoke(value, patchResources)) result = true
} }
} }
@ -230,30 +237,32 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* This is called after all patches that depend on [AddResourcesPatch] have been executed. * This is called after all patches that depend on [AddResourcesPatch] have been executed.
*/ */
override fun close() { override fun close() {
operator fun MutableMap<String, Pair<DomFileEditor, Node>>.invoke( operator fun MutableMap<String, Pair<Document, Node>>.invoke(
value: Value, value: Value,
resource: BaseResource resource: BaseResource,
) { ) {
// TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts // TODO: Fix open-closed principle violation by modifying BaseResource#serialize so that it accepts
// a Value and the map of editors. It will then get or put the editor suitable for its resource type // a Value and the map of documents. It will then get or put the document suitable for its resource type
// to serialize itself to it. // to serialize itself to it.
val resourceFileName = when (resource) { val resourceFileName =
is StringResource -> "strings" when (resource) {
is ArrayResource -> "arrays" is StringResource -> "strings"
else -> throw NotImplementedError("Unsupported resource type") is ArrayResource -> "arrays"
} else -> throw NotImplementedError("Unsupported resource type")
getOrPut(resourceFileName) {
val targetFile = context["res/$value/$resourceFileName.xml"].also {
it.parentFile?.mkdirs()
it.createNewFile()
} }
context.xmlEditor[targetFile.path].let { editor -> getOrPut(resourceFileName) {
val targetFile =
context.get("res/$value/$resourceFileName.xml", false).also {
it.parentFile?.mkdirs()
it.createNewFile()
}
context.document[targetFile.path].let { document ->
// Save the target node here as well // Save the target node here as well
// in order to avoid having to call editor.getNode("resources") // in order to avoid having to call document.getNode("resources")
// every time addUsingEditors is called but also save the editor so that it can be closed later. // but also save the document so that it can be closed later.
editor to editor.getNode("resources") document to document.getNode("resources")
} }
}.let { (_, targetNode) -> }.let { (_, targetNode) ->
targetNode.addResource(resource) { invoke(value, it) } targetNode.addResource(resource) { invoke(value, it) }
@ -261,17 +270,17 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
} }
forEach { (value, resources) -> forEach { (value, resources) ->
// A map of editors associated by their kind (e.g. strings, arrays). // A map of document associated by their kind (e.g. strings, arrays).
// Each editor is accompanied by the target node to which resources are added. // Each document is accompanied by the target node to which resources are added.
// A map is used because Map#getOrPut allows opening a new editor for the duration of a resource value. // A map is used because Map#getOrPut allows opening a new document for the duration of a resource value.
// This is done to prevent having to open the files for every resource that is added. // This is done to prevent having to open the files for every resource that is added.
// Instead, it is cached once and reused for resources of the same value. // Instead, it is cached once and reused for resources of the same value.
// This map is later accessed to close all editors for the current resource value. // This map is later accessed to close all documents for the current resource value.
val resourceFileEditors = mutableMapOf<String, Pair<DomFileEditor, Node>>() val documents = mutableMapOf<String, Pair<Document, Node>>()
resources.forEach { resource -> resourceFileEditors(value, resource) } resources.forEach { resource -> documents(value, resource) }
resourceFileEditors.values.forEach { (editor, _) -> editor.close() } documents.values.forEach { (document, _) -> document.close() }
} }
} }
} }

View file

@ -9,12 +9,12 @@ import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.Instruction import com.android.tools.smali.dexlib2.iface.instruction.Instruction
@Suppress("MemberVisibilityCanBePrivate") @Suppress("MemberVisibilityCanBePrivate")
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch() { abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
abstract fun filterMap( abstract fun filterMap(
classDef: ClassDef, classDef: ClassDef,
method: Method, method: Method,
instruction: Instruction, instruction: Instruction,
instructionIndex: Int instructionIndex: Int,
): T? ): T?
abstract fun transform(mutableMethod: MutableMethod, entry: T) abstract fun transform(mutableMethod: MutableMethod, entry: T)

View file

@ -8,16 +8,15 @@ import org.w3c.dom.Element
@Patch(description = "Sets allowAudioPlaybackCapture in manifest to true.") @Patch(description = "Sets allowAudioPlaybackCapture in manifest to true.")
internal object RemoveCaptureRestrictionResourcePatch : ResourcePatch() { internal object RemoveCaptureRestrictionResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// create an xml editor instance context.document["AndroidManifest.xml"].use { document ->
context.xmlEditor["AndroidManifest.xml"].use { dom ->
// get the application node // get the application node
val applicationNode = dom val applicationNode =
.file document
.getElementsByTagName("application") .getElementsByTagName("application")
.item(0) as Element .item(0) as Element
// set allowAudioPlaybackCapture attribute to true // set allowAudioPlaybackCapture attribute to true
applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true") applicationNode.setAttribute("android:allowAudioPlaybackCapture", "true")
} }
} }
} }

View file

@ -1,51 +0,0 @@
package app.revanced.patches.music.audio.exclusiveaudio.fingerprints
import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
@FuzzyPatternScanMethod(2) // FIXME: Test this threshold and find the best value.
internal object ExclusiveAudioFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.FINAL,
listOf("L", "Z"),
listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_EQ,
Opcode.CONST_4,
Opcode.GOTO,
Opcode.NOP,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IF_EQZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IF_EQZ,
Opcode.IF_EQZ,
Opcode.INVOKE_INTERFACE,
Opcode.INVOKE_INTERFACE,
Opcode.GOTO,
Opcode.RETURN_VOID
)
)

View file

@ -1,14 +1,12 @@
package app.revanced.patches.music.misc.gms.fingerprints package app.revanced.patches.music.misc.gms.fingerprints
import app.revanced.patcher.extensions.or import app.revanced.patcher.extensions.or
import app.revanced.patcher.fingerprint.annotation.FuzzyPatternScanMethod
import app.revanced.patcher.fingerprint.MethodFingerprint import app.revanced.patcher.fingerprint.MethodFingerprint
import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.AccessFlags
internal object ServiceCheckFingerprint : MethodFingerprint( internal object ServiceCheckFingerprint : MethodFingerprint(
"V", "V",
AccessFlags.PUBLIC or AccessFlags.STATIC, AccessFlags.PUBLIC or AccessFlags.STATIC,
listOf("L", "I"), listOf("L", "I"),
strings = listOf("Google Play Services not available") strings = listOf("Google Play Services not available"),
) )

View file

@ -6,8 +6,5 @@ import app.revanced.patches.shared.misc.integrations.BaseIntegrationsPatch
@Patch(requiresIntegrations = true) @Patch(requiresIntegrations = true)
object IntegrationsPatch : BaseIntegrationsPatch( object IntegrationsPatch : BaseIntegrationsPatch(
"Lapp/revanced/integrations/utils/ReVancedUtils;", setOf(ApplicationInitFingerprint),
setOf(
ApplicationInitFingerprint,
),
) )

View file

@ -6,23 +6,22 @@ import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element import org.w3c.dom.Element
@Patch( @Patch(
name = "Remove broadcasts restriction", name = "Remove broadcasts restriction",
description = "Enables starting/stopping NetGuard via broadcasts.", description = "Enables starting/stopping NetGuard via broadcasts.",
compatiblePackages = [CompatiblePackage("eu.faircode.netguard")], compatiblePackages = [CompatiblePackage("eu.faircode.netguard")],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object RemoveBroadcastsRestrictionPatch : ResourcePatch() { object RemoveBroadcastsRestrictionPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { dom -> context.document["AndroidManifest.xml"].use { document ->
val applicationNode = dom val applicationNode =
.file document
.getElementsByTagName("application") .getElementsByTagName("application")
.item(0) as Element .item(0) as Element
applicationNode.getElementsByTagName("receiver").also { list -> applicationNode.getElementsByTagName("receiver").also { list ->
for (i in 0 until list.length) { for (i in 0 until list.length) {
val element = list.item(i) as? Element ?: continue val element = list.item(i) as? Element ?: continue
if (element.getAttribute("android:name") == "eu.faircode.netguard.WidgetAdmin") { if (element.getAttribute("android:name") == "eu.faircode.netguard.WidgetAdmin") {

View file

@ -9,8 +9,8 @@ object HideBannerPatch : ResourcePatch() {
private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml" private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor[RESOURCE_FILE_PATH].use { context.document[RESOURCE_FILE_PATH].use {
it.file.getElementsByTagName("merge").item(0).childNodes.apply { it.getElementsByTagName("merge").item(0).childNodes.apply {
val attributes = arrayOf("height", "width") val attributes = arrayOf("height", "width")
for (i in 1 until length) { for (i in 1 until length) {
@ -30,4 +30,3 @@ object HideBannerPatch : ResourcePatch() {
} }
} }
} }

View file

@ -22,19 +22,21 @@ abstract class BaseGmsCoreSupportResourcePatch(
private val fromPackageName: String, private val fromPackageName: String,
private val toPackageName: String, private val toPackageName: String,
private val spoofedPackageSignature: String, private val spoofedPackageSignature: String,
dependencies: Set<PatchClass> = setOf() dependencies: Set<PatchClass> = setOf(),
) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class, AddResourcesPatch::class) + dependencies) { ) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class, AddResourcesPatch::class) + dependencies) {
internal val gmsCoreVendorOption = stringPatchOption( internal val gmsCoreVendorOption =
key = "gmsCoreVendor", stringPatchOption(
default = "com.mgoogle", key = "gmsCoreVendor",
values = mapOf( default = "com.mgoogle",
"Vanced" to "com.mgoogle", values =
"ReVanced" to "app.revanced" mapOf(
), "Vanced" to "com.mgoogle",
title = "GmsCore Vendor", "ReVanced" to "app.revanced",
description = "The group id of the GmsCore vendor.", ),
required = true title = "GmsCore Vendor",
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) } description = "The group id of the GmsCore vendor.",
required = true,
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) }
protected val gmsCoreVendor by gmsCoreVendorOption protected val gmsCoreVendor by gmsCoreVendorOption
@ -49,17 +51,20 @@ abstract class BaseGmsCoreSupportResourcePatch(
* Add metadata to manifest to support spoofing the package name and signature of GmsCore. * Add metadata to manifest to support spoofing the package name and signature of GmsCore.
*/ */
private fun ResourceContext.addSpoofingMetadata() { private fun ResourceContext.addSpoofingMetadata() {
fun Node.adoptChild(tagName: String, block: Element.() -> Unit) { fun Node.adoptChild(
tagName: String,
block: Element.() -> Unit,
) {
val child = ownerDocument.createElement(tagName) val child = ownerDocument.createElement(tagName)
child.block() child.block()
appendChild(child) appendChild(child)
} }
xmlEditor["AndroidManifest.xml"].use { document["AndroidManifest.xml"].use { document ->
val applicationNode = it val applicationNode =
.file document
.getElementsByTagName("application") .getElementsByTagName("application")
.item(0) .item(0)
// Spoof package name and signature. // Spoof package name and signature.
applicationNode.adoptChild("meta-data") { applicationNode.adoptChild("meta-data") {
@ -87,27 +92,27 @@ abstract class BaseGmsCoreSupportResourcePatch(
private fun ResourceContext.patchManifest() { private fun ResourceContext.patchManifest() {
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName) val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName)
val manifest = this["AndroidManifest.xml"].readText() val manifest = this.get("AndroidManifest.xml", false).readText()
this["AndroidManifest.xml"].writeText( this.get("AndroidManifest.xml", false).writeText(
manifest.replace( manifest.replace(
"package=\"$fromPackageName", "package=\"$fromPackageName",
"package=\"$packageName" "package=\"$packageName",
).replace( ).replace(
"android:authorities=\"$fromPackageName", "android:authorities=\"$fromPackageName",
"android:authorities=\"$packageName" "android:authorities=\"$packageName",
).replace( ).replace(
"$fromPackageName.permission.C2D_MESSAGE", "$fromPackageName.permission.C2D_MESSAGE",
"$packageName.permission.C2D_MESSAGE" "$packageName.permission.C2D_MESSAGE",
).replace( ).replace(
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION", "$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
"$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION" "$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
).replace( ).replace(
"com.google.android.c2dm", "com.google.android.c2dm",
"$gmsCoreVendor.android.c2dm" "$gmsCoreVendor.android.c2dm",
).replace( ).replace(
"</queries>", "</queries>",
"<package android:name=\"$gmsCoreVendor.android.gms\"/></queries>" "<package android:name=\"$gmsCoreVendor.android.gms\"/></queries>",
) ),
) )
} }
} }

View file

@ -7,7 +7,6 @@ import java.util.*
import java.util.concurrent.Executors import java.util.concurrent.Executors
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
object ResourceMappingPatch : ResourcePatch() { object ResourceMappingPatch : ResourcePatch() {
internal lateinit var resourceMappings: List<ResourceElement> internal lateinit var resourceMappings: List<ResourceElement>
private set private set
@ -17,15 +16,15 @@ object ResourceMappingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// save the file in memory to concurrently read from // save the file in memory to concurrently read from
val resourceXmlFile = context["res/values/public.xml"].readBytes() val resourceXmlFile = context.get("res/values/public.xml", false).readBytes()
// create a synchronized list to store the resource mappings // create a synchronized list to store the resource mappings
val mappings = Collections.synchronizedList(mutableListOf<ResourceElement>()) val mappings = Collections.synchronizedList(mutableListOf<ResourceElement>())
for (threadIndex in 0 until THREAD_COUNT) { for (threadIndex in 0 until THREAD_COUNT) {
threadPoolExecutor.execute thread@{ threadPoolExecutor.execute thread@{
context.xmlEditor[resourceXmlFile.inputStream()].use { editor -> context.document[resourceXmlFile.inputStream()].use { document ->
val resources = editor.file.documentElement.childNodes val resources = document.documentElement.childNodes
val resourcesLength = resources.length val resourcesLength = resources.length
val jobSize = resourcesLength / THREAD_COUNT val jobSize = resourcesLength / THREAD_COUNT
@ -59,4 +58,4 @@ object ResourceMappingPatch : ResourcePatch() {
} }
data class ResourceElement(val type: String, val name: String, val id: Long) data class ResourceElement(val type: String, val name: String, val id: Long)
} }

View file

@ -21,16 +21,18 @@ import java.io.Closeable
*/ */
abstract class BaseSettingsResourcePatch( abstract class BaseSettingsResourcePatch(
private val rootPreference: Pair<IntentPreference, String>? = null, private val rootPreference: Pair<IntentPreference, String>? = null,
dependencies: Set<PatchClass> = emptySet() dependencies: Set<PatchClass> = emptySet(),
) : ResourcePatch( ) : ResourcePatch(
dependencies = setOf(AddResourcesPatch::class) + dependencies dependencies = setOf(AddResourcesPatch::class) + dependencies,
), MutableSet<BasePreference> by mutableSetOf(), Closeable { ),
MutableSet<BasePreference> by mutableSetOf(),
Closeable {
private lateinit var context: ResourceContext private lateinit var context: ResourceContext
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.copyResources( context.copyResources(
"settings", "settings",
ResourceGroup("xml", "revanced_prefs.xml") ResourceGroup("xml", "revanced_prefs.xml"),
) )
this.context = context this.context = context
@ -49,14 +51,14 @@ abstract class BaseSettingsResourcePatch(
// Add the root preference to an existing fragment if needed. // Add the root preference to an existing fragment if needed.
rootPreference?.let { (intentPreference, fragment) -> rootPreference?.let { (intentPreference, fragment) ->
context.xmlEditor["res/xml/$fragment.xml"].use { context.document["res/xml/$fragment.xml"].use {
it.getNode("PreferenceScreen").addPreference(intentPreference) it.getNode("PreferenceScreen").addPreference(intentPreference)
} }
} }
// Add all preferences to the ReVanced fragment. // Add all preferences to the ReVanced fragment.
context.xmlEditor["res/xml/revanced_prefs.xml"].use { editor -> context.document["res/xml/revanced_prefs.xml"].use { document ->
val revancedPreferenceScreenNode = editor.getNode("PreferenceScreen") val revancedPreferenceScreenNode = document.getNode("PreferenceScreen")
forEach { revancedPreferenceScreenNode.addPreference(it) } forEach { revancedPreferenceScreenNode.addPreference(it) }
} }
} }

View file

@ -10,7 +10,7 @@ import org.w3c.dom.Element
@Patch( @Patch(
name = "Custom theme", name = "Custom theme",
description = "Applies a custom theme.", description = "Applies a custom theme.",
compatiblePackages = [CompatiblePackage("com.spotify.music")] compatiblePackages = [CompatiblePackage("com.spotify.music")],
) )
@Suppress("unused") @Suppress("unused")
object CustomThemePatch : ResourcePatch() { object CustomThemePatch : ResourcePatch() {
@ -19,7 +19,7 @@ object CustomThemePatch : ResourcePatch() {
default = "@android:color/black", default = "@android:color/black",
title = "Primary background color", title = "Primary background color",
description = "The background color. Can be a hex color or a resource reference.", description = "The background color. Can be a hex color or a resource reference.",
required = true required = true,
) )
private var backgroundColorSecondary by stringPatchOption( private var backgroundColorSecondary by stringPatchOption(
@ -27,7 +27,7 @@ object CustomThemePatch : ResourcePatch() {
default = "#ff282828", default = "#ff282828",
title = "Secondary background color", title = "Secondary background color",
description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.", description = "The secondary background color. (e.g. search box, artist & podcast). Can be a hex color or a resource reference.",
required = true required = true,
) )
private var accentColor by stringPatchOption( private var accentColor by stringPatchOption(
@ -35,16 +35,17 @@ object CustomThemePatch : ResourcePatch() {
default = "#ff1ed760", default = "#ff1ed760",
title = "Accent color", title = "Accent color",
description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.", description = "The accent color ('Spotify green' by default). Can be a hex color or a resource reference.",
required = true required = true,
) )
private var accentColorPressed by stringPatchOption( private var accentColorPressed by stringPatchOption(
key = "accentColorPressed", key = "accentColorPressed",
default = "#ff169c46", default = "#ff169c46",
title = "Pressed dark theme accent color", title = "Pressed dark theme accent color",
description = "The color when accented buttons are pressed, by default slightly darker than accent. " description =
+ "Can be a hex color or a resource reference.", "The color when accented buttons are pressed, by default slightly darker than accent. " +
required = true "Can be a hex color or a resource reference.",
required = true,
) )
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
@ -53,24 +54,26 @@ object CustomThemePatch : ResourcePatch() {
val accentColor = accentColor!! val accentColor = accentColor!!
val accentColorPressed = accentColorPressed!! val accentColorPressed = accentColorPressed!!
context.xmlEditor["res/values/colors.xml"].use { editor -> context.document["res/values/colors.xml"].use { document ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
for (i in 0 until resourcesNode.childNodes.length) { for (i in 0 until resourcesNode.childNodes.length) {
val node = resourcesNode.childNodes.item(i) as? Element ?: continue val node = resourcesNode.childNodes.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) { node.textContent =
"dark_base_background_elevated_base", "design_dark_default_color_background", when (node.getAttribute("name")) {
"design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer", "dark_base_background_elevated_base", "design_dark_default_color_background",
"sthlm_blk" -> backgroundColor "design_dark_default_color_surface", "gray_7", "gray_background", "gray_layer",
"sthlm_blk",
-> backgroundColor
"gray_15" -> backgroundColorSecondary "gray_15" -> backgroundColorSecondary
"dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor "dark_brightaccent_background_base", "dark_base_text_brightaccent", "green_light" -> accentColor
"dark_brightaccent_background_press" -> accentColorPressed "dark_brightaccent_background_press" -> accentColorPressed
else -> continue else -> continue
} }
} }
} }
} }

View file

@ -23,19 +23,19 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
dependencies = [IntegrationsPatch::class, SettingsPatch::class], dependencies = [IntegrationsPatch::class, SettingsPatch::class],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill"), CompatiblePackage("com.ss.android.ugc.trill"),
CompatiblePackage("com.zhiliaoapp.musically") CompatiblePackage("com.zhiliaoapp.musically"),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object SpoofSimPatch : BytecodePatch() { object SpoofSimPatch : BytecodePatch(emptySet()) {
private val replacements = hashMapOf( private val replacements = hashMapOf(
"getSimCountryIso" to "getCountryIso", "getSimCountryIso" to "getCountryIso",
"getNetworkCountryIso" to "getCountryIso", "getNetworkCountryIso" to "getCountryIso",
"getSimOperator" to "getOperator", "getSimOperator" to "getOperator",
"getNetworkOperator" to "getOperator", "getNetworkOperator" to "getOperator",
"getSimOperatorName" to "getOperatorName", "getSimOperatorName" to "getOperatorName",
"getNetworkOperatorName" to "getOperatorName" "getNetworkOperatorName" to "getOperatorName",
) )
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@ -85,7 +85,7 @@ object SpoofSimPatch : BytecodePatch() {
with(SettingsStatusLoadFingerprint.result!!.mutableMethod) { with(SettingsStatusLoadFingerprint.result!!.mutableMethod) {
addInstruction( addInstruction(
0, 0,
"invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableSimSpoof()V" "invoke-static {}, Lapp/revanced/integrations/tiktok/settings/SettingsStatus;->enableSimSpoof()V",
) )
} }
} }
@ -99,7 +99,7 @@ object SpoofSimPatch : BytecodePatch() {
""" """
invoke-static {v$resultReg}, Lapp/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String; invoke-static {v$resultReg}, Lapp/revanced/integrations/tiktok/spoof/sim/SpoofSimPatch;->$replacement(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$resultReg move-result-object v$resultReg
""" """,
) )
} }
} }

View file

@ -10,11 +10,11 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch
name = "Disable dashboard ads", name = "Disable dashboard ads",
description = "Disables ads in the dashboard.", description = "Disables ads in the dashboard.",
compatiblePackages = [CompatiblePackage("com.tumblr")], compatiblePackages = [CompatiblePackage("com.tumblr")],
dependencies = [TimelineFilterPatch::class] dependencies = [TimelineFilterPatch::class],
) )
@Suppress("unused") @Suppress("unused")
object DisableDashboardAds : BytecodePatch() { object DisableDashboardAds : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// The timeline object types are filtered by their name in the TimelineObjectType enum. // The timeline object types are filtered by their name in the TimelineObjectType enum.
// This is often different from the "object_type" returned in the api (noted in comments here) // This is often different from the "object_type" returned in the api (noted in comments here)
arrayOf( arrayOf(
@ -29,9 +29,9 @@ object DisableDashboardAds : BytecodePatch() {
"DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller" "DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller"
"DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video" "DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video"
"FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad" "FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad"
"GOOGLE_NATIVE" // "google_native_ad" "GOOGLE_NATIVE", // "google_native_ad"
).forEach { ).forEach {
TimelineFilterPatch.addObjectTypeFilter(it) TimelineFilterPatch.addObjectTypeFilter(it)
} }
} }
} }

View file

@ -10,13 +10,13 @@ import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch
name = "Disable in-app update", name = "Disable in-app update",
description = "Disables the in-app update check and update prompt.", description = "Disables the in-app update check and update prompt.",
dependencies = [OverrideFeatureFlagsPatch::class], dependencies = [OverrideFeatureFlagsPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")] compatiblePackages = [CompatiblePackage("com.tumblr")],
) )
@Suppress("unused") @Suppress("unused")
object DisableInAppUpdatePatch : BytecodePatch() { object DisableInAppUpdatePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// Before checking for updates using Google Play core AppUpdateManager, the value of this feature flag is checked. // Before checking for updates using Google Play core AppUpdateManager, the value of this feature flag is checked.
// If this flag is false or the last update check was today and no update check is performed. // If this flag is false or the last update check was today and no update check is performed.
OverrideFeatureFlagsPatch.addOverride("inAppUpdate", "false") OverrideFeatureFlagsPatch.addOverride("inAppUpdate", "false")
} }
} }

View file

@ -11,10 +11,10 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch
name = "Disable Tumblr Live", name = "Disable Tumblr Live",
description = "Disable the Tumblr Live tab button and dashboard carousel.", description = "Disable the Tumblr Live tab button and dashboard carousel.",
dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class], dependencies = [OverrideFeatureFlagsPatch::class, TimelineFilterPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")] compatiblePackages = [CompatiblePackage("com.tumblr")],
) )
@Suppress("unused") @Suppress("unused")
object DisableTumblrLivePatch : BytecodePatch() { object DisableTumblrLivePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// Hide the LIVE_MARQUEE timeline element that appears in the feed // Hide the LIVE_MARQUEE timeline element that appears in the feed
// Called "live_marquee" in api response // Called "live_marquee" in api response
@ -23,4 +23,4 @@ object DisableTumblrLivePatch : BytecodePatch() {
// Hide the Tab button for Tumblr Live by forcing the feature flag to false // Hide the Tab button for Tumblr Live by forcing the feature flag to false
OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false") OverrideFeatureFlagsPatch.addOverride("liveStreaming", "false")
} }
} }

View file

@ -11,12 +11,12 @@ import java.nio.file.Files
@Patch( @Patch(
name = "Dynamic color", name = "Dynamic color",
description = "Replaces the default X (Formerly Twitter) Blue with the user's Material You palette.", description = "Replaces the default X (Formerly Twitter) Blue with the user's Material You palette.",
compatiblePackages = [CompatiblePackage("com.twitter.android")] compatiblePackages = [CompatiblePackage("com.twitter.android")],
) )
@Suppress("unused") @Suppress("unused")
object DynamicColorPatch : ResourcePatch() { object DynamicColorPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
val resDirectory = context["res"] val resDirectory = context.get("res", false)
if (!resDirectory.isDirectory) throw PatchException("The res folder can not be found.") if (!resDirectory.isDirectory) throw PatchException("The res folder can not be found.")
val valuesV31Directory = resDirectory.resolve("values-v31") val valuesV31Directory = resDirectory.resolve("values-v31")
@ -28,16 +28,14 @@ object DynamicColorPatch : ResourcePatch() {
listOf(valuesV31Directory, valuesNightV31Directory).forEach { it -> listOf(valuesV31Directory, valuesNightV31Directory).forEach { it ->
val colorsXml = it.resolve("colors.xml") val colorsXml = it.resolve("colors.xml")
if(!colorsXml.exists()) { if (!colorsXml.exists()) {
FileWriter(colorsXml).use { FileWriter(colorsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>") it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
} }
} }
} }
context.xmlEditor["res/values-v31/colors.xml"].use { editor -> context.document["res/values-v31/colors.xml"].use { document ->
val document = editor.file
mapOf( mapOf(
"ps__twitter_blue" to "@color/twitter_blue", "ps__twitter_blue" to "@color/twitter_blue",
"ps__twitter_blue_pressed" to "@color/twitter_blue_fill_pressed", "ps__twitter_blue_pressed" to "@color/twitter_blue_fill_pressed",
@ -46,7 +44,7 @@ object DynamicColorPatch : ResourcePatch() {
"twitter_blue_opacity_30" to "@android:color/system_accent1_100", "twitter_blue_opacity_30" to "@android:color/system_accent1_100",
"twitter_blue_opacity_50" to "@android:color/system_accent1_200", "twitter_blue_opacity_50" to "@android:color/system_accent1_200",
"twitter_blue_opacity_58" to "@android:color/system_accent1_300", "twitter_blue_opacity_58" to "@android:color/system_accent1_300",
"deep_transparent_twitter_blue" to "@android:color/system_accent1_200" "deep_transparent_twitter_blue" to "@android:color/system_accent1_200",
).forEach { (k, v) -> ).forEach { (k, v) ->
val colorElement = document.createElement("color") val colorElement = document.createElement("color")
@ -57,16 +55,14 @@ object DynamicColorPatch : ResourcePatch() {
} }
} }
context.xmlEditor["res/values-night-v31/colors.xml"].use { editor -> context.document["res/values-night-v31/colors.xml"].use { document ->
val document = editor.file
mapOf( mapOf(
"twitter_blue" to "@android:color/system_accent1_200", "twitter_blue" to "@android:color/system_accent1_200",
"twitter_blue_fill_pressed" to "@android:color/system_accent1_300", "twitter_blue_fill_pressed" to "@android:color/system_accent1_300",
"twitter_blue_opacity_30" to "@android:color/system_accent1_50", "twitter_blue_opacity_30" to "@android:color/system_accent1_50",
"twitter_blue_opacity_50" to "@android:color/system_accent1_100", "twitter_blue_opacity_50" to "@android:color/system_accent1_100",
"twitter_blue_opacity_58" to "@android:color/system_accent1_200", "twitter_blue_opacity_58" to "@android:color/system_accent1_200",
"deep_transparent_twitter_blue" to "@android:color/system_accent1_200" "deep_transparent_twitter_blue" to "@android:color/system_accent1_200",
).forEach { (k, v) -> ).forEach { (k, v) ->
val colorElement = document.createElement("color") val colorElement = document.createElement("color")

View file

@ -4,7 +4,7 @@ import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patches.twitter.misc.hook.json.JsonHookPatch import app.revanced.patches.twitter.misc.hook.json.JsonHookPatch
abstract class BaseHookPatch(private val hookClassDescriptor: String) : BytecodePatch() { abstract class BaseHookPatch(private val hookClassDescriptor: String) : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) = override fun execute(context: BytecodeContext) =
JsonHookPatch.hooks.addHook(JsonHookPatch.Hook(context, hookClassDescriptor)) JsonHookPatch.hooks.addHook(JsonHookPatch.Hook(context, hookClassDescriptor))
} }

View file

@ -1,7 +1,5 @@
package app.revanced.patches.youtube.ad.general package app.revanced.patches.youtube.ad.general
import app.revanced.util.findMutableMethodOf
import app.revanced.util.injectHideViewCall
import app.revanced.patcher.data.BytecodeContext import app.revanced.patcher.data.BytecodeContext
import app.revanced.patcher.patch.BytecodePatch import app.revanced.patcher.patch.BytecodePatch
import app.revanced.patcher.patch.annotation.CompatiblePackage import app.revanced.patcher.patch.annotation.CompatiblePackage
@ -9,6 +7,8 @@ import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patches.shared.misc.fix.verticalscroll.VerticalScrollPatch import app.revanced.patches.shared.misc.fix.verticalscroll.VerticalScrollPatch
import app.revanced.patches.youtube.ad.getpremium.HideGetPremiumPatch import app.revanced.patches.youtube.ad.getpremium.HideGetPremiumPatch
import app.revanced.patches.youtube.misc.fix.backtoexitgesture.FixBackToExitGesturePatch import app.revanced.patches.youtube.misc.fix.backtoexitgesture.FixBackToExitGesturePatch
import app.revanced.util.findMutableMethodOf
import app.revanced.util.injectHideViewCall
import com.android.tools.smali.dexlib2.Opcode import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction31i
import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
@ -20,11 +20,12 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
HideGetPremiumPatch::class, HideGetPremiumPatch::class,
HideAdsResourcePatch::class, HideAdsResourcePatch::class,
VerticalScrollPatch::class, VerticalScrollPatch::class,
FixBackToExitGesturePatch::class FixBackToExitGesturePatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.32.39", "18.32.39",
"18.37.36", "18.37.36",
"18.38.44", "18.38.44",
@ -37,30 +38,33 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object HideAdsPatch : BytecodePatch() { object HideAdsPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
context.classes.forEach { classDef -> context.classes.forEach { classDef ->
classDef.methods.forEach { method -> classDef.methods.forEach { method ->
with(method.implementation) { with(method.implementation) {
this?.instructions?.forEachIndexed { index, instruction -> this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST) if (instruction.opcode != Opcode.CONST) {
return@forEachIndexed return@forEachIndexed
}
// Instruction to store the id adAttribution into a register // Instruction to store the id adAttribution into a register
if ((instruction as Instruction31i).wideLiteral != HideAdsResourcePatch.adAttributionId) if ((instruction as Instruction31i).wideLiteral != HideAdsResourcePatch.adAttributionId) {
return@forEachIndexed return@forEachIndexed
}
val insertIndex = index + 1 val insertIndex = index + 1
// Call to get the view with the id adAttribution // Call to get the view with the id adAttribution
with(instructions.elementAt(insertIndex)) { with(instructions.elementAt(insertIndex)) {
if (opcode != Opcode.INVOKE_VIRTUAL) if (opcode != Opcode.INVOKE_VIRTUAL) {
return@forEachIndexed return@forEachIndexed
}
// Hide the view // Hide the view
val viewRegister = (this as Instruction35c).registerC val viewRegister = (this as Instruction35c).registerC
@ -71,7 +75,7 @@ object HideAdsPatch : BytecodePatch() {
insertIndex, insertIndex,
viewRegister, viewRegister,
"Lapp/revanced/integrations/youtube/patches/components/AdsFilter;", "Lapp/revanced/integrations/youtube/patches/components/AdsFilter;",
"hideAdAttributionView" "hideAdAttributionView",
) )
} }
} }

View file

@ -13,28 +13,29 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
dependencies = [ dependencies = [
CopyVideoUrlResourcePatch::class, CopyVideoUrlResourcePatch::class,
PlayerControlsBytecodePatch::class, PlayerControlsBytecodePatch::class,
VideoInformationPatch::class VideoInformationPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object CopyVideoUrlBytecodePatch : BytecodePatch() { object CopyVideoUrlBytecodePatch : BytecodePatch(emptySet()) {
private const val INTEGRATIONS_PLAYER_PACKAGE = "Lapp/revanced/integrations/youtube/videoplayer" private const val INTEGRATIONS_PLAYER_PACKAGE = "Lapp/revanced/integrations/youtube/videoplayer"
private val BUTTONS_DESCRIPTORS = listOf( private val BUTTONS_DESCRIPTORS = listOf(
"$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlButton;", "$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlButton;",
"$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlTimestampButton;" "$INTEGRATIONS_PLAYER_PACKAGE/CopyVideoUrlTimestampButton;",
) )
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@ -44,4 +45,4 @@ object CopyVideoUrlBytecodePatch : BytecodePatch() {
PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V") PlayerControlsBytecodePatch.injectVisibilityCheckCall("$descriptor->changeVisibility(Z)V")
} }
} }
} }

View file

@ -13,24 +13,25 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
dependencies = [ dependencies = [
ExternalDownloadsResourcePatch::class, ExternalDownloadsResourcePatch::class,
PlayerControlsBytecodePatch::class, PlayerControlsBytecodePatch::class,
VideoInformationPatch::class VideoInformationPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
), ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object ExternalDownloadsBytecodePatch : BytecodePatch() { object ExternalDownloadsBytecodePatch : BytecodePatch(emptySet()) {
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;" private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
@ -39,13 +40,15 @@ object ExternalDownloadsBytecodePatch : BytecodePatch() {
*/ */
PlayerControlsBytecodePatch.initializeControl( PlayerControlsBytecodePatch.initializeControl(
"$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V") "$BUTTON_DESCRIPTOR->initializeButton(Landroid/view/View;)V",
)
/* /*
add code to change the visibility of the control add code to change the visibility of the control
*/ */
PlayerControlsBytecodePatch.injectVisibilityCheckCall( PlayerControlsBytecodePatch.injectVisibilityCheckCall(
"$BUTTON_DESCRIPTOR->changeVisibility(Z)V") "$BUTTON_DESCRIPTOR->changeVisibility(Z)V",
)
} }
} }

View file

@ -81,7 +81,7 @@ object CustomBrandingPatch : ResourcePatch() {
}.let { resourceGroups -> }.let { resourceGroups ->
if (icon != REVANCED_ICON) { if (icon != REVANCED_ICON) {
val path = File(icon) val path = File(icon)
val resourceDirectory = context["res"] val resourceDirectory = context.get("res", false)
resourceGroups.forEach { group -> resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName) val fromDirectory = path.resolve(group.resourceDirectoryName)
@ -102,7 +102,7 @@ object CustomBrandingPatch : ResourcePatch() {
appName?.let { name -> appName?.let { name ->
// Change the app name. // Change the app name.
val manifest = context["AndroidManifest.xml"] val manifest = context.get("AndroidManifest.xml", false)
manifest.writeText( manifest.writeText(
manifest.readText() manifest.readText()
.replace( .replace(

View file

@ -71,7 +71,7 @@ object ChangeHeaderPatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
// The directories to copy the header to. // The directories to copy the header to.
val targetResourceDirectories = targetResourceDirectoryNames.keys.mapNotNull { val targetResourceDirectories = targetResourceDirectoryNames.keys.mapNotNull {
context["res"].resolve(it).takeIf(File::exists) context.get("res", false).resolve(it).takeIf(File::exists)
} }
// The files to replace in the target directories. // The files to replace in the target directories.
val targetResourceFiles = targetResourceDirectoryNames.keys.map { directoryName -> val targetResourceFiles = targetResourceDirectoryNames.keys.map { directoryName ->
@ -120,7 +120,7 @@ object ChangeHeaderPatch : ResourcePatch() {
// For each source folder, copy the files to the target resource directories. // For each source folder, copy the files to the target resource directories.
sourceFolders.forEach { dpiSourceFolder -> sourceFolders.forEach { dpiSourceFolder ->
val targetDpiFolder = context["res"].resolve(dpiSourceFolder.name) val targetDpiFolder = context.get("res", false).resolve(dpiSourceFolder.name)
if (!targetDpiFolder.exists()) return@forEach if (!targetDpiFolder.exists()) return@forEach
val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!! val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!!

View file

@ -17,13 +17,13 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
dependencies = [ dependencies = [
IntegrationsPatch::class, IntegrationsPatch::class,
SettingsPatch::class, SettingsPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
], ],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage("com.google.android.youtube") CompatiblePackage("com.google.android.youtube"),
] ],
) )
object HideCastButtonPatch : BytecodePatch() { object HideCastButtonPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
@ -38,7 +38,7 @@ object HideCastButtonPatch : BytecodePatch() {
""" """
invoke-static {p1}, Lapp/revanced/integrations/youtube/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I invoke-static {p1}, Lapp/revanced/integrations/youtube/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I
move-result p1 move-result p1
""" """,
) )
} ?: throw PatchException("setVisibility method not found.") } ?: throw PatchException("setVisibility method not found.")
} }

View file

@ -12,7 +12,8 @@ import org.w3c.dom.Element
description = "Removes the dark background surrounding the video player controls.", description = "Removes the dark background surrounding the video player controls.",
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.32.39", "18.32.39",
"18.37.36", "18.37.36",
"18.38.44", "18.38.44",
@ -25,19 +26,19 @@ import org.w3c.dom.Element
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
], ],
use = false use = false,
) )
@Suppress("unused") @Suppress("unused")
object PlayerControlsBackgroundPatch : ResourcePatch() { object PlayerControlsBackgroundPatch : ResourcePatch() {
private const val RESOURCE_FILE_PATH = "res/drawable/player_button_circle_background.xml" private const val RESOURCE_FILE_PATH = "res/drawable/player_button_circle_background.xml"
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
context.xmlEditor[RESOURCE_FILE_PATH].use { editor -> context.document[RESOURCE_FILE_PATH].use { document ->
editor.file.doRecursively node@{ node -> document.doRecursively node@{ node ->
if (node !is Element) return@node if (node !is Element) return@node
node.getAttributeNode("android:color")?.let { attribute -> node.getAttributeNode("android:color")?.let { attribute ->

View file

@ -9,7 +9,7 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
import org.w3c.dom.Element import org.w3c.dom.Element
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class]) @Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class])
internal object SeekbarColorResourcePatch : ResourcePatch(){ internal object SeekbarColorResourcePatch : ResourcePatch() {
internal var reelTimeBarPlayedColorId = -1L internal var reelTimeBarPlayedColorId = -1L
internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L
internal var inlineTimeBarPlayedNotHighlightedColorId = -1L internal var inlineTimeBarPlayedNotHighlightedColorId = -1L
@ -18,7 +18,7 @@ internal object SeekbarColorResourcePatch : ResourcePatch(){
fun findColorResource(resourceName: String): Long { fun findColorResource(resourceName: String): Long {
return ResourceMappingPatch.resourceMappings return ResourceMappingPatch.resourceMappings
.find { it.type == "color" && it.name == resourceName }?.id .find { it.type == "color" && it.name == resourceName }?.id
?: throw PatchException("Could not find color resource: $resourceName") ?: throw PatchException("Could not find color resource: $resourceName")
} }
reelTimeBarPlayedColorId = reelTimeBarPlayedColorId =
@ -29,16 +29,18 @@ internal object SeekbarColorResourcePatch : ResourcePatch(){
findColorResource("inline_time_bar_played_not_highlighted_color") findColorResource("inline_time_bar_played_not_highlighted_color")
// Edit the resume playback drawable and replace the progress bar with a custom drawable // Edit the resume playback drawable and replace the progress bar with a custom drawable
context.xmlEditor["res/drawable/resume_playback_progressbar_drawable.xml"].use { editor -> context.document["res/drawable/resume_playback_progressbar_drawable.xml"].use { document ->
val layerList = editor.file.getElementsByTagName("layer-list").item(0) as Element val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element val progressNode = layerList.getElementsByTagName("item").item(1) as Element
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) { if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
throw PatchException("Could not find progress bar") throw PatchException("Could not find progress bar")
} }
val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element
val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element
val replacementNode = editor.file.createElement( val replacementNode =
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable") document.createElement(
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable",
)
scaleNode.replaceChild(replacementNode, shapeNode) scaleNode.replaceChild(replacementNode, shapeNode)
} }
} }

View file

@ -17,26 +17,25 @@ import app.revanced.util.inputStreamFromBundledResource
dependencies = [ dependencies = [
SettingsPatch::class, SettingsPatch::class,
ResourceMappingPatch::class, ResourceMappingPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
] ],
) )
internal object SponsorBlockResourcePatch : ResourcePatch() { internal object SponsorBlockResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences( SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
IntentPreference( IntentPreference(
"revanced_sb_settings", "revanced_sb_settings",
intent = SettingsPatch.newIntent("revanced_sb_settings_intent") intent = SettingsPatch.newIntent("revanced_sb_settings_intent"),
) ),
) )
arrayOf( arrayOf(
ResourceGroup( ResourceGroup(
"layout", "layout",
"revanced_sb_inline_sponsor_overlay.xml", "revanced_sb_inline_sponsor_overlay.xml",
"revanced_sb_new_segment.xml", "revanced_sb_new_segment.xml",
"revanced_sb_skip_sponsor_button.xml" "revanced_sb_skip_sponsor_button.xml",
), ),
ResourceGroup( ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist // required resource for back button, because when the base APK is used, this resource will not exist
@ -46,37 +45,47 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
"revanced_sb_edit.xml", "revanced_sb_edit.xml",
"revanced_sb_logo.xml", "revanced_sb_logo.xml",
"revanced_sb_publish.xml", "revanced_sb_publish.xml",
"revanced_sb_voting.xml" "revanced_sb_voting.xml",
), ),
ResourceGroup( ResourceGroup(
// required resource for back button, because when the base APK is used, this resource will not exist // required resource for back button, because when the base APK is used, this resource will not exist
"drawable-xxxhdpi", "quantum_ic_skip_next_white_24.png" "drawable-xxxhdpi",
) "quantum_ic_skip_next_white_24.png",
),
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("sponsorblock", resourceGroup) context.copyResources("sponsorblock", resourceGroup)
} }
// copy nodes from host resources to their real xml files // copy nodes from host resources to their real xml files
val hostingResourceStream = inputStreamFromBundledResource( val hostingResourceStream =
"sponsorblock", inputStreamFromBundledResource(
"host/layout/youtube_controls_layout.xml" "sponsorblock",
)!! "host/layout/youtube_controls_layout.xml",
)!!
var modifiedControlsLayout = false var modifiedControlsLayout = false
val targetXmlEditor = context.xmlEditor["res/layout/youtube_controls_layout.xml"] val targetDocument = context.document["res/layout/youtube_controls_layout.xml"]
"RelativeLayout".copyXmlNode( "RelativeLayout".copyXmlNode(
context.xmlEditor[hostingResourceStream], context.document[hostingResourceStream],
targetXmlEditor targetDocument,
).also { ).also {
val children = targetXmlEditor.file.getElementsByTagName("RelativeLayout").item(0).childNodes val children = targetDocument.getElementsByTagName("RelativeLayout").item(0).childNodes
// Replace the startOf with the voting button view so that the button does not overlap // Replace the startOf with the voting button view so that the button does not overlap
for (i in 1 until children.length) { for (i in 1 until children.length) {
val view = children.item(i) val view = children.item(i)
// Replace the attribute for a specific node only // Replace the attribute for a specific node only
if (!(view.hasAttributes() && view.attributes.getNamedItem("android:id").nodeValue.endsWith("live_chat_overlay_button"))) continue if (!(
view.hasAttributes() &&
view.attributes.getNamedItem(
"android:id",
).nodeValue.endsWith("live_chat_overlay_button")
)
) {
continue
}
// voting button id from the voting button view from the youtube_controls_layout.xml host file // voting button id from the voting button view from the youtube_controls_layout.xml host file
val votingButtonId = "@+id/revanced_sb_voting_button" val votingButtonId = "@+id/revanced_sb_voting_button"
@ -90,4 +99,4 @@ internal object SponsorBlockResourcePatch : ResourcePatch() {
if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout") if (!modifiedControlsLayout) throw PatchException("Could not modify controls layout")
} }
} }

View file

@ -20,8 +20,8 @@ import org.w3c.dom.Element
SettingsPatch::class, SettingsPatch::class,
ResourceMappingPatch::class, ResourceMappingPatch::class,
SeekbarPreferencesPatch::class, SeekbarPreferencesPatch::class,
AddResourcesPatch::class AddResourcesPatch::class,
] ],
) )
internal object ThemeResourcePatch : ResourcePatch() { internal object ThemeResourcePatch : ResourcePatch() {
private const val SPLASH_BACKGROUND_COLOR = "revanced_splash_background_color" private const val SPLASH_BACKGROUND_COLOR = "revanced_splash_background_color"
@ -31,28 +31,29 @@ internal object ThemeResourcePatch : ResourcePatch() {
SeekbarPreferencesPatch.addPreferences( SeekbarPreferencesPatch.addPreferences(
SwitchPreference("revanced_seekbar_custom_color"), SwitchPreference("revanced_seekbar_custom_color"),
TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS) TextPreference("revanced_seekbar_custom_color_value", inputType = InputType.TEXT_CAP_CHARACTERS),
) )
// Edit theme colors via resources. // Edit theme colors via resources.
context.xmlEditor["res/values/colors.xml"].use { editor -> context.document["res/values/colors.xml"].use { document ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val children = resourcesNode.childNodes val children = resourcesNode.childNodes
for (i in 0 until children.length) { for (i in 0 until children.length) {
val node = children.item(i) as? Element ?: continue val node = children.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) { node.textContent =
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3", when (node.getAttribute("name")) {
"yt_black4", "yt_status_bar_background_dark", "material_grey_850" "yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
-> darkThemeBackgroundColor ?: continue "yt_black4", "yt_status_bar_background_dark", "material_grey_850",
-> darkThemeBackgroundColor ?: continue
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98", "yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
"yt_white2", "yt_white3", "yt_white4", "yt_white2", "yt_white3", "yt_white4",
-> lightThemeBackgroundColor ?: continue -> lightThemeBackgroundColor ?: continue
else -> continue else -> continue
} }
} }
} }
@ -68,14 +69,15 @@ internal object ThemeResourcePatch : ResourcePatch() {
// Edit splash screen files and change the background color, // Edit splash screen files and change the background color,
// if the background colors are set. // if the background colors are set.
if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) { if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) {
val splashScreenResourceFiles = listOf( val splashScreenResourceFiles =
"res/drawable/quantum_launchscreen_youtube.xml", listOf(
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml" "res/drawable/quantum_launchscreen_youtube.xml",
) "res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
)
splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile -> splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile ->
context.xmlEditor[resourceFile].use { context.document[resourceFile].use {
val layerList = it.file.getElementsByTagName("layer-list").item(0) as Element val layerList = it.getElementsByTagName("layer-list").item(0) as Element
val childNodes = layerList.childNodes val childNodes = layerList.childNodes
for (i in 0 until childNodes.length) { for (i in 0 until childNodes.length) {
@ -89,24 +91,24 @@ internal object ThemeResourcePatch : ResourcePatch() {
} }
} }
} }
} }
private fun addColorResource( private fun addColorResource(
context: ResourceContext, context: ResourceContext,
resourceFile: String, resourceFile: String,
colorName: String, colorName: String,
colorValue: String colorValue: String,
) { ) {
context.xmlEditor[resourceFile].use { context.document[resourceFile].use {
val resourcesNode = it.file.getElementsByTagName("resources").item(0) as Element val resourcesNode = it.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild( resourcesNode.appendChild(
it.file.createElement("color").apply { it.createElement("color").apply {
setAttribute("name", colorName) setAttribute("name", colorName)
setAttribute("category", "color") setAttribute("category", "color")
textContent = colorValue textContent = colorValue
}) },
)
} }
} }
} }

View file

@ -3,7 +3,7 @@ package app.revanced.patches.youtube.misc.playercontrols
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.patch.ResourcePatch import app.revanced.patcher.patch.ResourcePatch
import app.revanced.patcher.patch.annotation.Patch import app.revanced.patcher.patch.annotation.Patch
import app.revanced.patcher.util.DomFileEditor import app.revanced.patcher.util.Document
import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch import app.revanced.patches.shared.misc.mapping.ResourceMappingPatch
import java.io.Closeable import java.io.Closeable
@ -18,14 +18,15 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
private var lastLeftOf = "fullscreen_button" private var lastLeftOf = "fullscreen_button"
private lateinit var resourceContext: ResourceContext private lateinit var resourceContext: ResourceContext
private lateinit var targetXmlEditor: DomFileEditor private lateinit var targetDocument: Document
override fun execute(context: ResourceContext) { override fun execute(context: ResourceContext) {
resourceContext = context resourceContext = context
targetXmlEditor = context.xmlEditor[TARGET_RESOURCE] targetDocument = context.document[TARGET_RESOURCE]
bottomUiContainerResourceId = ResourceMappingPatch.resourceMappings bottomUiContainerResourceId =
.single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id ResourceMappingPatch.resourceMappings
.single { it.type == "id" && it.name == "bottom_ui_container_stub" }.id
} }
/** /**
@ -34,18 +35,18 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
* @param resourceDirectoryName The name of the directory containing the hosting resource. * @param resourceDirectoryName The name of the directory containing the hosting resource.
*/ */
fun addControls(resourceDirectoryName: String) { fun addControls(resourceDirectoryName: String) {
val sourceXmlEditor = resourceContext.xmlEditor[ val sourceDocument =
this::class.java.classLoader.getResourceAsStream( resourceContext.document[
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME" this::class.java.classLoader.getResourceAsStream(
)!! "$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
] )!!,
]
val targetElement = "android.support.constraint.ConstraintLayout" val targetElement = "android.support.constraint.ConstraintLayout"
val hostElements = sourceXmlEditor.file.getElementsByTagName(targetElement).item(0).childNodes val hostElements = sourceDocument.getElementsByTagName(targetElement).item(0).childNodes
val destinationResourceFile = targetXmlEditor.file val destinationElement = targetDocument.getElementsByTagName(targetElement).item(0)
val destinationElement = destinationResourceFile.getElementsByTagName(targetElement).item(0)
for (index in 1 until hostElements.length) { for (index in 1 until hostElements.length) {
val element = hostElements.item(index).cloneNode(true) val element = hostElements.item(index).cloneNode(true)
@ -63,11 +64,11 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength) lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
// Add the element. // Add the element.
destinationResourceFile.adoptNode(element) targetDocument.adoptNode(element)
destinationElement.appendChild(element) destinationElement.appendChild(element)
} }
sourceXmlEditor.close() sourceDocument.close()
} }
override fun close() = targetXmlEditor.close() override fun close() = targetDocument.close()
} }

View file

@ -12,12 +12,13 @@ import org.w3c.dom.Element
object SettingsResourcePatch : BaseSettingsResourcePatch( object SettingsResourcePatch : BaseSettingsResourcePatch(
IntentPreference( IntentPreference(
"revanced_settings", "revanced_settings",
intent = SettingsPatch.newIntent("revanced_settings_intent") intent = SettingsPatch.newIntent("revanced_settings_intent"),
) to "settings_fragment", ) to "settings_fragment",
dependencies = setOf( dependencies =
ResourceMappingPatch::class, setOf(
AddResourcesPatch::class, ResourceMappingPatch::class,
) AddResourcesPatch::class,
),
) { ) {
// Used for a fingerprint from SettingsPatch. // Used for a fingerprint from SettingsPatch.
internal var appearanceStringId = -1L internal var appearanceStringId = -1L
@ -28,12 +29,13 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
AddResourcesPatch(this::class) AddResourcesPatch(this::class)
// Used for a fingerprint from SettingsPatch. // Used for a fingerprint from SettingsPatch.
appearanceStringId = ResourceMappingPatch.resourceMappings.find { appearanceStringId =
it.type == "string" && it.name == "app_theme_appearance_dark" ResourceMappingPatch.resourceMappings.find {
}!!.id it.type == "string" && it.name == "app_theme_appearance_dark"
}!!.id
arrayOf( arrayOf(
ResourceGroup("layout", "revanced_settings_with_toolbar.xml") ResourceGroup("layout", "revanced_settings_with_toolbar.xml"),
).forEach { resourceGroup -> ).forEach { resourceGroup ->
context.copyResources("settings", resourceGroup) context.copyResources("settings", resourceGroup)
} }
@ -41,20 +43,20 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
// Modify the manifest and add a data intent filter to the LicenseActivity. // Modify the manifest and add a data intent filter to the LicenseActivity.
// Some devices freak out if undeclared data is passed to an intent, // Some devices freak out if undeclared data is passed to an intent,
// and this change appears to fix the issue. // and this change appears to fix the issue.
context.xmlEditor["AndroidManifest.xml"].use { editor -> context.document["AndroidManifest.xml"].use { document ->
// A xml regular-expression would probably work better than this manual searching. // A xml regular-expression would probably work better than this manual searching.
val manifestNodes = editor.file.getElementsByTagName("manifest").item(0).childNodes val manifestNodes = document.getElementsByTagName("manifest").item(0).childNodes
for (i in 0..manifestNodes.length) { for (i in 0..manifestNodes.length) {
val node = manifestNodes.item(i) val node = manifestNodes.item(i)
if (node != null && node.nodeName == "application") { if (node != null && node.nodeName == "application") {
val applicationNodes = node.childNodes val applicationNodes = node.childNodes
for (j in 0..applicationNodes.length) { for (j in 0..applicationNodes.length) {
val applicationChild = applicationNodes.item(j) val applicationChild = applicationNodes.item(j)
if (applicationChild is Element && applicationChild.nodeName == "activity" if (applicationChild is Element && applicationChild.nodeName == "activity" &&
&& applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity" applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity"
) { ) {
val intentFilter = editor.file.createElement("intent-filter") val intentFilter = document.createElement("intent-filter")
val mimeType = editor.file.createElement("data") val mimeType = document.createElement("data")
mimeType.setAttribute("android:mimeType", "text/plain") mimeType.setAttribute("android:mimeType", "text/plain")
intentFilter.appendChild(mimeType) intentFilter.appendChild(mimeType)
applicationChild.appendChild(intentFilter) applicationChild.appendChild(intentFilter)
@ -65,4 +67,4 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
} }
} }
} }
} }

View file

@ -13,20 +13,21 @@ import app.revanced.patches.youtube.video.speed.remember.RememberPlaybackSpeedPa
dependencies = [CustomPlaybackSpeedPatch::class, RememberPlaybackSpeedPatch::class], dependencies = [CustomPlaybackSpeedPatch::class, RememberPlaybackSpeedPatch::class],
compatiblePackages = [ compatiblePackages = [
CompatiblePackage( CompatiblePackage(
"com.google.android.youtube", [ "com.google.android.youtube",
[
"18.48.39", "18.48.39",
"18.49.37", "18.49.37",
"19.01.34", "19.01.34",
"19.02.39", "19.02.39",
"19.03.35", "19.03.35",
"19.03.36", "19.03.36",
"19.04.37" "19.04.37",
] ],
) ),
] ],
) )
@Suppress("unused") @Suppress("unused")
object PlaybackSpeedPatch : BytecodePatch() { object PlaybackSpeedPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) { override fun execute(context: BytecodeContext) {
// All patches this patch depends on succeed. // All patches this patch depends on succeed.
} }

View file

@ -1,8 +1,8 @@
package app.revanced.util package app.revanced.util
import app.revanced.patcher.data.ResourceContext import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.util.Document
import app.revanced.patcher.util.DomFileEditor import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.util.resource.BaseResource import app.revanced.util.resource.BaseResource
import org.w3c.dom.Node import org.w3c.dom.Node
import org.w3c.dom.NodeList import org.w3c.dom.NodeList
@ -25,9 +25,10 @@ fun Node.childElementsSequence() = this.childNodes.asSequence().filter { it.node
/** /**
* Performs the given [action] on each child element. * Performs the given [action] on each child element.
*/ */
fun Node.forEachChildElement(action: (Node) -> Unit) = childElementsSequence().forEach { fun Node.forEachChildElement(action: (Node) -> Unit) =
action(it) childElementsSequence().forEach {
} action(it)
}
/** /**
* Recursively traverse the DOM tree starting from the given root node. * Recursively traverse the DOM tree starting from the given root node.
@ -45,15 +46,19 @@ fun Node.doRecursively(action: (Node) -> Unit) {
* @param sourceResourceDirectory The source resource directory name. * @param sourceResourceDirectory The source resource directory name.
* @param resources The resources to copy. * @param resources The resources to copy.
*/ */
fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resources: ResourceGroup) { fun ResourceContext.copyResources(
val targetResourceDirectory = this["res"] sourceResourceDirectory: String,
vararg resources: ResourceGroup,
) {
val targetResourceDirectory = this.get("res", false)
for (resourceGroup in resources) { for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource -> resourceGroup.resources.forEach { resource ->
val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource" val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
Files.copy( Files.copy(
inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!, inputStreamFromBundledResource(sourceResourceDirectory, resourceFile)!!,
targetResourceDirectory.resolve(resourceFile).toPath(), StandardCopyOption.REPLACE_EXISTING targetResourceDirectory.resolve(resourceFile).toPath(),
StandardCopyOption.REPLACE_EXISTING,
) )
} }
} }
@ -61,7 +66,7 @@ fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resour
internal fun inputStreamFromBundledResource( internal fun inputStreamFromBundledResource(
sourceResourceDirectory: String, sourceResourceDirectory: String,
resourceFile: String resourceFile: String,
): InputStream? = classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile") ): InputStream? = classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile")
/** /**
@ -80,29 +85,29 @@ class ResourceGroup(val resourceDirectoryName: String, vararg val resources: Str
fun ResourceContext.iterateXmlNodeChildren( fun ResourceContext.iterateXmlNodeChildren(
resource: String, resource: String,
targetTag: String, targetTag: String,
callback: (node: Node) -> Unit callback: (node: Node) -> Unit,
) = ) = document[classLoader.getResourceAsStream(resource)!!].use {
xmlEditor[classLoader.getResourceAsStream(resource)!!].use { val stringsNode = it.getElementsByTagName(targetTag).item(0).childNodes
val stringsNode = it.file.getElementsByTagName(targetTag).item(0).childNodes for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i)) }
}
/** /**
* Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor]. * Copies the specified node of the source [Document] to the target [Document].
* @param source the source [DomFileEditor]. * @param source the source [Document].
* @param target the target [DomFileEditor]- * @param target the target [Document]-
* @return AutoCloseable that closes the target [DomFileEditor]s. * @return AutoCloseable that closes the [Document]s.
*/ */
fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable { fun String.copyXmlNode(
val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes source: Document,
target: Document,
): AutoCloseable {
val hostNodes = source.getElementsByTagName(this).item(0).childNodes
val destinationResourceFile = target.file val destinationNode = target.getElementsByTagName(this).item(0)
val destinationNode = destinationResourceFile.getElementsByTagName(this).item(0)
for (index in 0 until hostNodes.length) { for (index in 0 until hostNodes.length) {
val node = hostNodes.item(index).cloneNode(true) val node = hostNodes.item(index).cloneNode(true)
destinationResourceFile.adoptNode(node) target.adoptNode(node)
destinationNode.appendChild(node) destinationNode.appendChild(node)
} }
@ -112,14 +117,30 @@ fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoClosea
} }
} }
@Deprecated(
"Use copyXmlNode(Document, Document) instead.",
ReplaceWith(
"this.copyXmlNode(source.file as Document, target.file as Document)",
"app.revanced.patcher.util.Document",
"app.revanced.patcher.util.Document",
),
)
fun String.copyXmlNode(
source: DomFileEditor,
target: DomFileEditor,
) = this.copyXmlNode(source.file as Document, target.file as Document)
/** /**
* Add a resource node child. * Add a resource node child.
* *
* @param resource The resource to add. * @param resource The resource to add.
* @param resourceCallback Called when a resource has been processed. * @param resourceCallback Called when a resource has been processed.
*/ */
internal fun Node.addResource(resource: BaseResource, resourceCallback: (BaseResource) -> Unit = { }) { internal fun Node.addResource(
resource: BaseResource,
resourceCallback: (BaseResource) -> Unit = { },
) {
appendChild(resource.serialize(ownerDocument, resourceCallback)) appendChild(resource.serialize(ownerDocument, resourceCallback))
} }
internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0) internal fun Document?.getNode(tagName: String) = this!!.getElementsByTagName(tagName).item(0)