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

View file

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

View file

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

View file

@ -11,16 +11,15 @@ import java.io.File
name = "Override certificate pinning",
description = "Overrides certificate pinning, allowing to inspect traffic via a proxy.",
dependencies = [EnableAndroidDebuggingPatch::class],
use = false
use = false,
)
@Suppress("unused")
object OverrideCertificatePinningPatch : ResourcePatch() {
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.
context.xmlEditor["AndroidManifest.xml"].use { editor ->
val document = editor.file
context.document["AndroidManifest.xml"].use { document ->
val applicationNode = document.getElementsByTagName("application").item(0) as Element
if (!applicationNode.hasAttribute("networkSecurityConfig")) {
@ -54,7 +53,7 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
</trust-anchors>
</debug-overrides>
</network-security-config>
"""
""",
)
} else {
// If the file already exists.
@ -63,12 +62,11 @@ object OverrideCertificatePinningPatch : ResourcePatch() {
writeText(
text.replace(
"<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,17 +11,18 @@ import java.io.Closeable
@Patch(
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.",
use = false
use = false,
)
@Suppress("unused")
object ChangePackageNamePatch : ResourcePatch(), Closeable {
private val packageNameOption = stringPatchOption(
private val packageNameOption =
stringPatchOption(
key = "packageName",
default = "Default",
values = mapOf("Default" to "Default"),
title = "Package name",
description = "The name of the package to rename the app to.",
required = true
required = true,
) {
it == "Default" || it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$"))
}
@ -43,20 +44,25 @@ object ChangePackageNamePatch : ResourcePatch(), Closeable {
fun setOrGetFallbackPackageName(fallbackPackageName: String): String {
val packageName = packageNameOption.value!!
return if (packageName == packageNameOption.default)
return if (packageName == packageNameOption.default) {
fallbackPackageName.also { packageNameOption.value = it }
else
} else {
packageName
}
}
override fun close() = context.xmlEditor["AndroidManifest.xml"].use { editor ->
override fun close() =
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(
"package",
if (replacementPackageName != packageNameOption.default) replacementPackageName
else "${manifest.getAttribute("package")}.revanced"
if (replacementPackageName != packageNameOption.default) {
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.ResourcePatch
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.util.*
import app.revanced.util.resource.ArrayResource
@ -19,6 +19,7 @@ import java.util.*
* An identifier of an app. For example, `youtube`.
*/
private typealias AppId = String
/**
* 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.
*/
private typealias PatchResources = MutableSet<BaseResource>
/**
* A map of resources belonging to a patch.
*/
private typealias AppResources = MutableMap<PatchId, PatchResources>
/**
* A map of resources belonging to an app.
*/
@ -67,7 +70,8 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
override fun execute(context: ResourceContext) {
this.context = context
resources = buildMap {
resources =
buildMap {
/**
* Puts resources under `/resources/addresources/<value>/<resourceKind>.xml` into the map.
*
@ -82,14 +86,14 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
) {
inputStreamFromBundledResource(
"addresources",
"$value/$resourceKind.xml"
"$value/$resourceKind.xml",
)?.let { stream ->
// Add the resources associated with the given value to the map,
// instead of overwriting it.
// This covers the example case such as adding strings and arrays of the same value.
getOrPut(value, ::mutableMapOf).apply {
context.xmlEditor[stream].use {
it.file.getElementsByTagName("app").asSequence().forEach { app ->
context.document[stream].use {
it.getElementsByTagName("app").asSequence().forEach { app ->
val appId = app.attributes.getNamedItem("id").textContent
getOrPut(appId, ::mutableMapOf).apply {
@ -136,8 +140,10 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
*
* @return True if the resource was added, false if it already existed.
*/
operator fun invoke(value: Value, resource: BaseResource) =
getOrPut(value, ::mutableSetOf).add(resource)
operator fun invoke(
value: Value,
resource: BaseResource,
) = getOrPut(value, ::mutableSetOf).add(resource)
/**
* 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.
*/
operator fun invoke(value: Value, resources: Iterable<BaseResource>) =
getOrPut(value, ::mutableSetOf).addAll(resources)
operator fun invoke(
value: Value,
resources: Iterable<BaseResource>,
) = getOrPut(value, ::mutableSetOf).addAll(resources)
/**
* Adds a [StringResource].
@ -177,10 +185,9 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
*/
operator fun invoke(
name: String,
items: List<String>
items: List<String>,
) = invoke("values", ArrayResource(name, items))
/**
* 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
}
}
},
): Boolean {
val (appId, patchId) = patch.parseIds()
@ -230,30 +237,32 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
* This is called after all patches that depend on [AddResourcesPatch] have been executed.
*/
override fun close() {
operator fun MutableMap<String, Pair<DomFileEditor, Node>>.invoke(
operator fun MutableMap<String, Pair<Document, Node>>.invoke(
value: Value,
resource: BaseResource
resource: BaseResource,
) {
// 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.
val resourceFileName = when (resource) {
val resourceFileName =
when (resource) {
is StringResource -> "strings"
is ArrayResource -> "arrays"
else -> throw NotImplementedError("Unsupported resource type")
}
getOrPut(resourceFileName) {
val targetFile = context["res/$value/$resourceFileName.xml"].also {
val targetFile =
context.get("res/$value/$resourceFileName.xml", false).also {
it.parentFile?.mkdirs()
it.createNewFile()
}
context.xmlEditor[targetFile.path].let { editor ->
context.document[targetFile.path].let { document ->
// Save the target node here as well
// in order to avoid having to call editor.getNode("resources")
// every time addUsingEditors is called but also save the editor so that it can be closed later.
editor to editor.getNode("resources")
// in order to avoid having to call document.getNode("resources")
// but also save the document so that it can be closed later.
document to document.getNode("resources")
}
}.let { (_, targetNode) ->
targetNode.addResource(resource) { invoke(value, it) }
@ -261,17 +270,17 @@ object AddResourcesPatch : ResourcePatch(), MutableMap<Value, MutableSet<BaseRes
}
forEach { (value, resources) ->
// A map of editors associated by their kind (e.g. strings, arrays).
// Each editor 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 of document associated by their kind (e.g. strings, arrays).
// Each document is accompanied by the target node to which resources are added.
// 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.
// 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.
val resourceFileEditors = mutableMapOf<String, Pair<DomFileEditor, Node>>()
// This map is later accessed to close all documents for the current resource value.
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
@Suppress("MemberVisibilityCanBePrivate")
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch() {
abstract class BaseTransformInstructionsPatch<T> : BytecodePatch(emptySet()) {
abstract fun filterMap(
classDef: ClassDef,
method: Method,
instruction: Instruction,
instructionIndex: Int
instructionIndex: Int,
): T?
abstract fun transform(mutableMethod: MutableMethod, entry: T)

View file

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

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
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
internal object ServiceCheckFingerprint : MethodFingerprint(
"V",
AccessFlags.PUBLIC or AccessFlags.STATIC,
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)
object IntegrationsPatch : BaseIntegrationsPatch(
"Lapp/revanced/integrations/utils/ReVancedUtils;",
setOf(
ApplicationInitFingerprint,
),
setOf(ApplicationInitFingerprint),
)

View file

@ -6,19 +6,18 @@ import app.revanced.patcher.patch.annotation.CompatiblePackage
import app.revanced.patcher.patch.annotation.Patch
import org.w3c.dom.Element
@Patch(
name = "Remove broadcasts restriction",
description = "Enables starting/stopping NetGuard via broadcasts.",
compatiblePackages = [CompatiblePackage("eu.faircode.netguard")],
use = false
use = false,
)
@Suppress("unused")
object RemoveBroadcastsRestrictionPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
context.xmlEditor["AndroidManifest.xml"].use { dom ->
val applicationNode = dom
.file
context.document["AndroidManifest.xml"].use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0) as Element

View file

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

View file

@ -22,18 +22,20 @@ abstract class BaseGmsCoreSupportResourcePatch(
private val fromPackageName: String,
private val toPackageName: String,
private val spoofedPackageSignature: String,
dependencies: Set<PatchClass> = setOf()
dependencies: Set<PatchClass> = setOf(),
) : ResourcePatch(dependencies = setOf(ChangePackageNamePatch::class, AddResourcesPatch::class) + dependencies) {
internal val gmsCoreVendorOption = stringPatchOption(
internal val gmsCoreVendorOption =
stringPatchOption(
key = "gmsCoreVendor",
default = "com.mgoogle",
values = mapOf(
values =
mapOf(
"Vanced" to "com.mgoogle",
"ReVanced" to "app.revanced"
"ReVanced" to "app.revanced",
),
title = "GmsCore Vendor",
description = "The group id of the GmsCore vendor.",
required = true
required = true,
) { it!!.matches(Regex("^[a-z]\\w*(\\.[a-z]\\w*)+\$")) }
protected val gmsCoreVendor by gmsCoreVendorOption
@ -49,15 +51,18 @@ abstract class BaseGmsCoreSupportResourcePatch(
* Add metadata to manifest to support spoofing the package name and signature of GmsCore.
*/
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)
child.block()
appendChild(child)
}
xmlEditor["AndroidManifest.xml"].use {
val applicationNode = it
.file
document["AndroidManifest.xml"].use { document ->
val applicationNode =
document
.getElementsByTagName("application")
.item(0)
@ -87,27 +92,27 @@ abstract class BaseGmsCoreSupportResourcePatch(
private fun ResourceContext.patchManifest() {
val packageName = ChangePackageNamePatch.setOrGetFallbackPackageName(toPackageName)
val manifest = this["AndroidManifest.xml"].readText()
this["AndroidManifest.xml"].writeText(
val manifest = this.get("AndroidManifest.xml", false).readText()
this.get("AndroidManifest.xml", false).writeText(
manifest.replace(
"package=\"$fromPackageName",
"package=\"$packageName"
"package=\"$packageName",
).replace(
"android:authorities=\"$fromPackageName",
"android:authorities=\"$packageName"
"android:authorities=\"$packageName",
).replace(
"$fromPackageName.permission.C2D_MESSAGE",
"$packageName.permission.C2D_MESSAGE"
"$packageName.permission.C2D_MESSAGE",
).replace(
"$fromPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
"$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
"$packageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
).replace(
"com.google.android.c2dm",
"$gmsCoreVendor.android.c2dm"
"$gmsCoreVendor.android.c2dm",
).replace(
"</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.TimeUnit
object ResourceMappingPatch : ResourcePatch() {
internal lateinit var resourceMappings: List<ResourceElement>
private set
@ -17,15 +16,15 @@ object ResourceMappingPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
// 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
val mappings = Collections.synchronizedList(mutableListOf<ResourceElement>())
for (threadIndex in 0 until THREAD_COUNT) {
threadPoolExecutor.execute thread@{
context.xmlEditor[resourceXmlFile.inputStream()].use { editor ->
val resources = editor.file.documentElement.childNodes
context.document[resourceXmlFile.inputStream()].use { document ->
val resources = document.documentElement.childNodes
val resourcesLength = resources.length
val jobSize = resourcesLength / THREAD_COUNT

View file

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

View file

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

View file

@ -23,19 +23,19 @@ import com.android.tools.smali.dexlib2.iface.reference.MethodReference
dependencies = [IntegrationsPatch::class, SettingsPatch::class],
compatiblePackages = [
CompatiblePackage("com.ss.android.ugc.trill"),
CompatiblePackage("com.zhiliaoapp.musically")
CompatiblePackage("com.zhiliaoapp.musically"),
],
use = false
use = false,
)
@Suppress("unused")
object SpoofSimPatch : BytecodePatch() {
object SpoofSimPatch : BytecodePatch(emptySet()) {
private val replacements = hashMapOf(
"getSimCountryIso" to "getCountryIso",
"getNetworkCountryIso" to "getCountryIso",
"getSimOperator" to "getOperator",
"getNetworkOperator" to "getOperator",
"getSimOperatorName" to "getOperatorName",
"getNetworkOperatorName" to "getOperatorName"
"getNetworkOperatorName" to "getOperatorName",
)
override fun execute(context: BytecodeContext) {
@ -85,7 +85,7 @@ object SpoofSimPatch : BytecodePatch() {
with(SettingsStatusLoadFingerprint.result!!.mutableMethod) {
addInstruction(
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;
move-result-object v$resultReg
"""
""",
)
}
}

View file

@ -10,10 +10,10 @@ import app.revanced.patches.tumblr.timelinefilter.TimelineFilterPatch
name = "Disable dashboard ads",
description = "Disables ads in the dashboard.",
compatiblePackages = [CompatiblePackage("com.tumblr")],
dependencies = [TimelineFilterPatch::class]
dependencies = [TimelineFilterPatch::class],
)
@Suppress("unused")
object DisableDashboardAds : BytecodePatch() {
object DisableDashboardAds : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
// 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)
@ -29,7 +29,7 @@ object DisableDashboardAds : BytecodePatch() {
"DISPLAY_IO_INTERSCROLLER_AD", // "display_io_interscroller"
"DISPLAY_IO_HEADLINE_VIDEO_AD", // "display_io_headline_video"
"FACEBOOK_BIDDAABLE", // "facebook_biddable_sdk_ad"
"GOOGLE_NATIVE" // "google_native_ad"
"GOOGLE_NATIVE", // "google_native_ad"
).forEach {
TimelineFilterPatch.addObjectTypeFilter(it)
}

View file

@ -10,10 +10,10 @@ import app.revanced.patches.tumblr.featureflags.OverrideFeatureFlagsPatch
name = "Disable in-app update",
description = "Disables the in-app update check and update prompt.",
dependencies = [OverrideFeatureFlagsPatch::class],
compatiblePackages = [CompatiblePackage("com.tumblr")]
compatiblePackages = [CompatiblePackage("com.tumblr")],
)
@Suppress("unused")
object DisableInAppUpdatePatch : BytecodePatch() {
object DisableInAppUpdatePatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
// 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.

View file

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

View file

@ -11,12 +11,12 @@ import java.nio.file.Files
@Patch(
name = "Dynamic color",
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")
object DynamicColorPatch : ResourcePatch() {
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.")
val valuesV31Directory = resDirectory.resolve("values-v31")
@ -28,16 +28,14 @@ object DynamicColorPatch : ResourcePatch() {
listOf(valuesV31Directory, valuesNightV31Directory).forEach { it ->
val colorsXml = it.resolve("colors.xml")
if(!colorsXml.exists()) {
if (!colorsXml.exists()) {
FileWriter(colorsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
}
}
}
context.xmlEditor["res/values-v31/colors.xml"].use { editor ->
val document = editor.file
context.document["res/values-v31/colors.xml"].use { document ->
mapOf(
"ps__twitter_blue" to "@color/twitter_blue",
"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_50" to "@android:color/system_accent1_200",
"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) ->
val colorElement = document.createElement("color")
@ -57,16 +55,14 @@ object DynamicColorPatch : ResourcePatch() {
}
}
context.xmlEditor["res/values-night-v31/colors.xml"].use { editor ->
val document = editor.file
context.document["res/values-night-v31/colors.xml"].use { document ->
mapOf(
"twitter_blue" to "@android:color/system_accent1_200",
"twitter_blue_fill_pressed" to "@android:color/system_accent1_300",
"twitter_blue_opacity_30" to "@android:color/system_accent1_50",
"twitter_blue_opacity_50" to "@android:color/system_accent1_100",
"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) ->
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.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) =
JsonHookPatch.hooks.addHook(JsonHookPatch.Hook(context, hookClassDescriptor))
}

View file

@ -1,7 +1,5 @@
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.patch.BytecodePatch
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.youtube.ad.getpremium.HideGetPremiumPatch
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.iface.instruction.formats.Instruction31i
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,
HideAdsResourcePatch::class,
VerticalScrollPatch::class,
FixBackToExitGesturePatch::class
FixBackToExitGesturePatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube", [
"com.google.android.youtube",
[
"18.32.39",
"18.37.36",
"18.38.44",
@ -37,30 +38,33 @@ import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c
"19.02.39",
"19.03.35",
"19.03.36",
"19.04.37"
]
)
]
"19.04.37",
],
),
],
)
@Suppress("unused")
object HideAdsPatch : BytecodePatch() {
object HideAdsPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
context.classes.forEach { classDef ->
classDef.methods.forEach { method ->
with(method.implementation) {
this?.instructions?.forEachIndexed { index, instruction ->
if (instruction.opcode != Opcode.CONST)
if (instruction.opcode != Opcode.CONST) {
return@forEachIndexed
}
// 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
}
val insertIndex = index + 1
// Call to get the view with the id adAttribution
with(instructions.elementAt(insertIndex)) {
if (opcode != Opcode.INVOKE_VIRTUAL)
if (opcode != Opcode.INVOKE_VIRTUAL) {
return@forEachIndexed
}
// Hide the view
val viewRegister = (this as Instruction35c).registerC
@ -71,7 +75,7 @@ object HideAdsPatch : BytecodePatch() {
insertIndex,
viewRegister,
"Lapp/revanced/integrations/youtube/patches/components/AdsFilter;",
"hideAdAttributionView"
"hideAdAttributionView",
)
}
}

View file

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

View file

@ -13,24 +13,25 @@ import app.revanced.patches.youtube.video.information.VideoInformationPatch
dependencies = [
ExternalDownloadsResourcePatch::class,
PlayerControlsBytecodePatch::class,
VideoInformationPatch::class
VideoInformationPatch::class,
],
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube", [
"com.google.android.youtube",
[
"18.48.39",
"18.49.37",
"19.01.34",
"19.02.39",
"19.03.35",
"19.03.36",
"19.04.37"
]
"19.04.37",
],
),
]
],
)
@Suppress("unused")
object ExternalDownloadsBytecodePatch : BytecodePatch() {
object ExternalDownloadsBytecodePatch : BytecodePatch(emptySet()) {
private const val BUTTON_DESCRIPTOR = "Lapp/revanced/integrations/youtube/videoplayer/ExternalDownloadButton;"
override fun execute(context: BytecodeContext) {
@ -39,13 +40,15 @@ object ExternalDownloadsBytecodePatch : BytecodePatch() {
*/
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
*/
PlayerControlsBytecodePatch.injectVisibilityCheckCall(
"$BUTTON_DESCRIPTOR->changeVisibility(Z)V")
"$BUTTON_DESCRIPTOR->changeVisibility(Z)V",
)
}
}

View file

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

View file

@ -71,7 +71,7 @@ object ChangeHeaderPatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
// The directories to copy the header to.
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.
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.
sourceFolders.forEach { dpiSourceFolder ->
val targetDpiFolder = context["res"].resolve(dpiSourceFolder.name)
val targetDpiFolder = context.get("res", false).resolve(dpiSourceFolder.name)
if (!targetDpiFolder.exists()) return@forEach
val imgSourceFiles = dpiSourceFolder.listFiles { file -> file.isFile }!!

View file

@ -17,13 +17,13 @@ import app.revanced.patches.youtube.misc.settings.SettingsPatch
dependencies = [
IntegrationsPatch::class,
SettingsPatch::class,
AddResourcesPatch::class
AddResourcesPatch::class,
],
compatiblePackages = [
CompatiblePackage("com.google.android.youtube")
]
CompatiblePackage("com.google.android.youtube"),
],
)
object HideCastButtonPatch : BytecodePatch() {
object HideCastButtonPatch : BytecodePatch(emptySet()) {
override fun execute(context: BytecodeContext) {
AddResourcesPatch(this::class)
@ -38,7 +38,7 @@ object HideCastButtonPatch : BytecodePatch() {
"""
invoke-static {p1}, Lapp/revanced/integrations/youtube/patches/HideCastButtonPatch;->getCastButtonOverrideV2(I)I
move-result p1
"""
""",
)
} ?: 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.",
compatiblePackages = [
CompatiblePackage(
"com.google.android.youtube", [
"com.google.android.youtube",
[
"18.32.39",
"18.37.36",
"18.38.44",
@ -25,19 +26,19 @@ import org.w3c.dom.Element
"19.02.39",
"19.03.35",
"19.03.36",
"19.04.37"
]
)
"19.04.37",
],
use = false
),
],
use = false,
)
@Suppress("unused")
object PlayerControlsBackgroundPatch : ResourcePatch() {
private const val RESOURCE_FILE_PATH = "res/drawable/player_button_circle_background.xml"
override fun execute(context: ResourceContext) {
context.xmlEditor[RESOURCE_FILE_PATH].use { editor ->
editor.file.doRecursively node@{ node ->
context.document[RESOURCE_FILE_PATH].use { document ->
document.doRecursively node@{ node ->
if (node !is Element) return@node
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
@Patch(dependencies = [SettingsPatch::class, ResourceMappingPatch::class])
internal object SeekbarColorResourcePatch : ResourcePatch(){
internal object SeekbarColorResourcePatch : ResourcePatch() {
internal var reelTimeBarPlayedColorId = -1L
internal var inlineTimeBarColorizedBarPlayedColorDarkId = -1L
internal var inlineTimeBarPlayedNotHighlightedColorId = -1L
@ -29,16 +29,18 @@ internal object SeekbarColorResourcePatch : ResourcePatch(){
findColorResource("inline_time_bar_played_not_highlighted_color")
// 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 ->
val layerList = editor.file.getElementsByTagName("layer-list").item(0) as Element
context.document["res/drawable/resume_playback_progressbar_drawable.xml"].use { document ->
val layerList = document.getElementsByTagName("layer-list").item(0) as Element
val progressNode = layerList.getElementsByTagName("item").item(1) as Element
if (!progressNode.getAttributeNode("android:id").value.endsWith("progress")) {
throw PatchException("Could not find progress bar")
}
val scaleNode = progressNode.getElementsByTagName("scale").item(0) as Element
val shapeNode = scaleNode.getElementsByTagName("shape").item(0) as Element
val replacementNode = editor.file.createElement(
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable")
val replacementNode =
document.createElement(
"app.revanced.integrations.youtube.patches.theme.ProgressBarDrawable",
)
scaleNode.replaceChild(replacementNode, shapeNode)
}
}

View file

@ -17,26 +17,25 @@ import app.revanced.util.inputStreamFromBundledResource
dependencies = [
SettingsPatch::class,
ResourceMappingPatch::class,
AddResourcesPatch::class
]
AddResourcesPatch::class,
],
)
internal object SponsorBlockResourcePatch : ResourcePatch() {
override fun execute(context: ResourceContext) {
AddResourcesPatch(this::class)
SettingsPatch.PreferenceScreen.LAYOUT.addPreferences(
IntentPreference(
"revanced_sb_settings",
intent = SettingsPatch.newIntent("revanced_sb_settings_intent")
)
intent = SettingsPatch.newIntent("revanced_sb_settings_intent"),
),
)
arrayOf(
ResourceGroup(
"layout",
"revanced_sb_inline_sponsor_overlay.xml",
"revanced_sb_new_segment.xml",
"revanced_sb_skip_sponsor_button.xml"
"revanced_sb_skip_sponsor_button.xml",
),
ResourceGroup(
// 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_logo.xml",
"revanced_sb_publish.xml",
"revanced_sb_voting.xml"
"revanced_sb_voting.xml",
),
ResourceGroup(
// 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 ->
context.copyResources("sponsorblock", resourceGroup)
}
// copy nodes from host resources to their real xml files
val hostingResourceStream = inputStreamFromBundledResource(
val hostingResourceStream =
inputStreamFromBundledResource(
"sponsorblock",
"host/layout/youtube_controls_layout.xml"
"host/layout/youtube_controls_layout.xml",
)!!
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(
context.xmlEditor[hostingResourceStream],
targetXmlEditor
context.document[hostingResourceStream],
targetDocument,
).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
for (i in 1 until children.length) {
val view = children.item(i)
// 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
val votingButtonId = "@+id/revanced_sb_voting_button"

View file

@ -20,8 +20,8 @@ import org.w3c.dom.Element
SettingsPatch::class,
ResourceMappingPatch::class,
SeekbarPreferencesPatch::class,
AddResourcesPatch::class
]
AddResourcesPatch::class,
],
)
internal object ThemeResourcePatch : ResourcePatch() {
private const val SPLASH_BACKGROUND_COLOR = "revanced_splash_background_color"
@ -31,20 +31,21 @@ internal object ThemeResourcePatch : ResourcePatch() {
SeekbarPreferencesPatch.addPreferences(
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.
context.xmlEditor["res/values/colors.xml"].use { editor ->
val resourcesNode = editor.file.getElementsByTagName("resources").item(0) as Element
context.document["res/values/colors.xml"].use { document ->
val resourcesNode = document.getElementsByTagName("resources").item(0) as Element
val children = resourcesNode.childNodes
for (i in 0 until children.length) {
val node = children.item(i) as? Element ?: continue
node.textContent = when (node.getAttribute("name")) {
node.textContent =
when (node.getAttribute("name")) {
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
"yt_black4", "yt_status_bar_background_dark", "material_grey_850"
"yt_black4", "yt_status_bar_background_dark", "material_grey_850",
-> darkThemeBackgroundColor ?: continue
"yt_white1", "yt_white1_opacity95", "yt_white1_opacity98",
@ -68,14 +69,15 @@ internal object ThemeResourcePatch : ResourcePatch() {
// Edit splash screen files and change the background color,
// if the background colors are set.
if (darkThemeBackgroundColor != null && lightThemeBackgroundColor != null) {
val splashScreenResourceFiles = listOf(
val splashScreenResourceFiles =
listOf(
"res/drawable/quantum_launchscreen_youtube.xml",
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml"
"res/drawable-sw600dp/quantum_launchscreen_youtube.xml",
)
splashScreenResourceFiles.forEach editSplashScreen@{ resourceFile ->
context.xmlEditor[resourceFile].use {
val layerList = it.file.getElementsByTagName("layer-list").item(0) as Element
context.document[resourceFile].use {
val layerList = it.getElementsByTagName("layer-list").item(0) as Element
val childNodes = layerList.childNodes
for (i in 0 until childNodes.length) {
@ -89,24 +91,24 @@ internal object ThemeResourcePatch : ResourcePatch() {
}
}
}
}
private fun addColorResource(
context: ResourceContext,
resourceFile: String,
colorName: String,
colorValue: String
colorValue: String,
) {
context.xmlEditor[resourceFile].use {
val resourcesNode = it.file.getElementsByTagName("resources").item(0) as Element
context.document[resourceFile].use {
val resourcesNode = it.getElementsByTagName("resources").item(0) as Element
resourcesNode.appendChild(
it.file.createElement("color").apply {
it.createElement("color").apply {
setAttribute("name", colorName)
setAttribute("category", "color")
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.patch.ResourcePatch
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 java.io.Closeable
@ -18,13 +18,14 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
private var lastLeftOf = "fullscreen_button"
private lateinit var resourceContext: ResourceContext
private lateinit var targetXmlEditor: DomFileEditor
private lateinit var targetDocument: Document
override fun execute(context: ResourceContext) {
resourceContext = context
targetXmlEditor = context.xmlEditor[TARGET_RESOURCE]
targetDocument = context.document[TARGET_RESOURCE]
bottomUiContainerResourceId = ResourceMappingPatch.resourceMappings
bottomUiContainerResourceId =
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.
*/
fun addControls(resourceDirectoryName: String) {
val sourceXmlEditor = resourceContext.xmlEditor[
val sourceDocument =
resourceContext.document[
this::class.java.classLoader.getResourceAsStream(
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME"
)!!
"$resourceDirectoryName/host/layout/$TARGET_RESOURCE_NAME",
)!!,
]
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 = destinationResourceFile.getElementsByTagName(targetElement).item(0)
val destinationElement = targetDocument.getElementsByTagName(targetElement).item(0)
for (index in 1 until hostElements.length) {
val element = hostElements.item(index).cloneNode(true)
@ -63,11 +64,11 @@ object BottomControlsResourcePatch : ResourcePatch(), Closeable {
lastLeftOf = element.attributes.getNamedItem("android:id").nodeValue.substring(nameSpaceLength)
// Add the element.
destinationResourceFile.adoptNode(element)
targetDocument.adoptNode(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(
IntentPreference(
"revanced_settings",
intent = SettingsPatch.newIntent("revanced_settings_intent")
intent = SettingsPatch.newIntent("revanced_settings_intent"),
) to "settings_fragment",
dependencies = setOf(
dependencies =
setOf(
ResourceMappingPatch::class,
AddResourcesPatch::class,
)
),
) {
// Used for a fingerprint from SettingsPatch.
internal var appearanceStringId = -1L
@ -28,12 +29,13 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
AddResourcesPatch(this::class)
// Used for a fingerprint from SettingsPatch.
appearanceStringId = ResourceMappingPatch.resourceMappings.find {
appearanceStringId =
ResourceMappingPatch.resourceMappings.find {
it.type == "string" && it.name == "app_theme_appearance_dark"
}!!.id
arrayOf(
ResourceGroup("layout", "revanced_settings_with_toolbar.xml")
ResourceGroup("layout", "revanced_settings_with_toolbar.xml"),
).forEach { resourceGroup ->
context.copyResources("settings", resourceGroup)
}
@ -41,20 +43,20 @@ object SettingsResourcePatch : BaseSettingsResourcePatch(
// Modify the manifest and add a data intent filter to the LicenseActivity.
// Some devices freak out if undeclared data is passed to an intent,
// 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.
val manifestNodes = editor.file.getElementsByTagName("manifest").item(0).childNodes
val manifestNodes = document.getElementsByTagName("manifest").item(0).childNodes
for (i in 0..manifestNodes.length) {
val node = manifestNodes.item(i)
if (node != null && node.nodeName == "application") {
val applicationNodes = node.childNodes
for (j in 0..applicationNodes.length) {
val applicationChild = applicationNodes.item(j)
if (applicationChild is Element && applicationChild.nodeName == "activity"
&& applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity"
if (applicationChild is Element && applicationChild.nodeName == "activity" &&
applicationChild.getAttribute("android:name") == "com.google.android.libraries.social.licenses.LicenseActivity"
) {
val intentFilter = editor.file.createElement("intent-filter")
val mimeType = editor.file.createElement("data")
val intentFilter = document.createElement("intent-filter")
val mimeType = document.createElement("data")
mimeType.setAttribute("android:mimeType", "text/plain")
intentFilter.appendChild(mimeType)
applicationChild.appendChild(intentFilter)

View file

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

View file

@ -1,8 +1,8 @@
package app.revanced.util
import app.revanced.patcher.data.ResourceContext
import app.revanced.patcher.util.Document
import app.revanced.patcher.util.DomFileEditor
import app.revanced.patches.all.misc.resources.AddResourcesPatch
import app.revanced.util.resource.BaseResource
import org.w3c.dom.Node
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.
*/
fun Node.forEachChildElement(action: (Node) -> Unit) = childElementsSequence().forEach {
fun Node.forEachChildElement(action: (Node) -> Unit) =
childElementsSequence().forEach {
action(it)
}
}
/**
* 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 resources The resources to copy.
*/
fun ResourceContext.copyResources(sourceResourceDirectory: String, vararg resources: ResourceGroup) {
val targetResourceDirectory = this["res"]
fun ResourceContext.copyResources(
sourceResourceDirectory: String,
vararg resources: ResourceGroup,
) {
val targetResourceDirectory = this.get("res", false)
for (resourceGroup in resources) {
resourceGroup.resources.forEach { resource ->
val resourceFile = "${resourceGroup.resourceDirectoryName}/$resource"
Files.copy(
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(
sourceResourceDirectory: String,
resourceFile: String
resourceFile: String,
): InputStream? = classLoader.getResourceAsStream("$sourceResourceDirectory/$resourceFile")
/**
@ -80,29 +85,29 @@ class ResourceGroup(val resourceDirectoryName: String, vararg val resources: Str
fun ResourceContext.iterateXmlNodeChildren(
resource: String,
targetTag: String,
callback: (node: Node) -> Unit
) =
xmlEditor[classLoader.getResourceAsStream(resource)!!].use {
val stringsNode = it.file.getElementsByTagName(targetTag).item(0).childNodes
callback: (node: Node) -> Unit,
) = document[classLoader.getResourceAsStream(resource)!!].use {
val stringsNode = it.getElementsByTagName(targetTag).item(0).childNodes
for (i in 1 until stringsNode.length - 1) callback(stringsNode.item(i))
}
}
/**
* Copies the specified node of the source [DomFileEditor] to the target [DomFileEditor].
* @param source the source [DomFileEditor].
* @param target the target [DomFileEditor]-
* @return AutoCloseable that closes the target [DomFileEditor]s.
* Copies the specified node of the source [Document] to the target [Document].
* @param source the source [Document].
* @param target the target [Document]-
* @return AutoCloseable that closes the [Document]s.
*/
fun String.copyXmlNode(source: DomFileEditor, target: DomFileEditor): AutoCloseable {
val hostNodes = source.file.getElementsByTagName(this).item(0).childNodes
fun String.copyXmlNode(
source: Document,
target: Document,
): AutoCloseable {
val hostNodes = source.getElementsByTagName(this).item(0).childNodes
val destinationResourceFile = target.file
val destinationNode = destinationResourceFile.getElementsByTagName(this).item(0)
val destinationNode = target.getElementsByTagName(this).item(0)
for (index in 0 until hostNodes.length) {
val node = hostNodes.item(index).cloneNode(true)
destinationResourceFile.adoptNode(node)
target.adoptNode(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.
*
* @param resource The resource to add.
* @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))
}
internal fun DomFileEditor?.getNode(tagName: String) = this!!.file.getElementsByTagName(tagName).item(0)
internal fun Document?.getNode(tagName: String) = this!!.getElementsByTagName(tagName).item(0)