refactor: Bump ReVanced Patcher & merge integrations by using ReVanced Patches Gradle plugin

BREAKING CHANGE: ReVanced Patcher >= 21 required
This commit is contained in:
inotia00
2024-12-07 22:13:39 +09:00
parent f074c3ecc5
commit b31865afbe
2706 changed files with 64970 additions and 30705 deletions

View File

@ -0,0 +1,62 @@
package app.revanced.generator
import app.revanced.patcher.patch.Package
import app.revanced.patcher.patch.Patch
import com.google.gson.GsonBuilder
import java.io.File
internal class JsonPatchesFileGenerator : PatchesFileGenerator {
override fun generate(patches: Set<Patch<*>>) {
val patchesJson = File("../patches.json")
patches.sortedBy { it.name }.map {
JsonPatch(
it.name!!,
it.description,
it.compatiblePackages,
it.use,
it.options.values.map { option ->
JsonPatch.Option(
option.key,
option.default,
option.values,
option.title,
option.description,
option.required,
)
},
)
}.let {
patchesJson.writeText(GsonBuilder().serializeNulls().create().toJson(it))
}
patchesJson.writeText(
patchesJson.readText()
.replace(
"\"first\":",
"\"name\":"
).replace(
"\"second\":",
"\"versions\":"
)
)
}
@Suppress("unused")
private class JsonPatch(
val name: String? = null,
val description: String? = null,
val compatiblePackages: Set<Package>? = null,
val use: Boolean = true,
val options: List<Option>,
) {
class Option(
val key: String,
val default: Any?,
val values: Map<String, Any?>?,
val title: String?,
val description: String?,
val required: Boolean,
)
}
}

View File

@ -0,0 +1,20 @@
package app.revanced.generator
import app.revanced.patcher.patch.loadPatchesFromJar
import java.io.File
internal fun main() = loadPatchesFromJar(
setOf(File("build/libs/").listFiles { file ->
val fileName = file.name
!fileName.contains("javadoc") &&
!fileName.contains("sources") &&
fileName.endsWith(".rvp")
}!!.first()),
).also { loader ->
if (loader.isEmpty()) throw IllegalStateException("No patches found")
}.let { bundle ->
arrayOf(
JsonPatchesFileGenerator(),
ReadMeFileGenerator()
).forEach { generator -> generator.generate(bundle) }
}

View File

@ -0,0 +1,7 @@
package app.revanced.generator
import app.revanced.patcher.patch.Patch
internal interface PatchesFileGenerator {
fun generate(patches: Set<Patch<*>>)
}

View File

@ -0,0 +1,116 @@
package app.revanced.generator
import app.revanced.patcher.patch.Patch
import java.io.File
import java.io.PrintWriter
import java.nio.file.Files
import java.nio.file.Paths
internal class ReadMeFileGenerator : PatchesFileGenerator {
// For this exception to apply to [README.md],
// Supported version of [app.revanced.patches.music.utils.integrations.Constants.COMPATIBLE_PACKAGE] should be empty.
private val exception = mapOf(
"com.google.android.apps.youtube.music" to "6.29.59"
)
private val tableHeader =
"| \uD83D\uDC8A Patch | \uD83D\uDCDC Description | \uD83C\uDFF9 Target Version |\n" +
"|:--------:|:--------------:|:-----------------:|"
override fun generate(patches: Set<Patch<*>>) {
val rootPath = Paths.get("").toAbsolutePath().parent!!
val readMeFilePath = "$rootPath/README.md"
val readMeFile = File(readMeFilePath)
val readMeTemplateFile = File("$rootPath/README-template.md")
val output = StringBuilder()
if (readMeFile.exists()) {
PrintWriter(readMeFile).also {
it.print("")
it.close()
}
} else {
Files.createFile(Paths.get(readMeFilePath))
}
// copy the contents of 'README-template.md' to the temp file
StringBuilder(readMeTemplateFile.readText())
.toString()
.let(readMeFile::writeText)
// add a list of supported versions to a temp file
mapOf(
app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE to "\"COMPATIBLE_PACKAGE_MUSIC\"",
app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE to "\"COMPATIBLE_PACKAGE_REDDIT\"",
app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE to "\"COMPATIBLE_PACKAGE_YOUTUBE\""
).forEach { (compatiblePackage, replaceString) ->
compatiblePackage.let { (packageName, versions) ->
val supportedVersion =
if (versions == null && exception.containsKey(packageName)) {
exception[packageName] + "+"
} else {
versions
?.toString()
?.replace("[", "[\n \"")
?.replace("]", "\"\n ]")
?.replace(", ", "\",\n \"")
?: "\"ALL\""
}
StringBuilder(readMeFile.readText())
.replace(Regex(replaceString), supportedVersion)
.let(readMeFile::writeText)
}
mutableMapOf<String, MutableSet<Patch<*>>>()
.apply {
for (patch in patches) {
patch.compatiblePackages?.forEach { (packageName, _) ->
if (!contains(packageName)) put(packageName, mutableSetOf())
this[packageName]!!.add(patch)
}
}
}
.entries
.sortedByDescending { it.value.size }
.forEach { (pkg, patches) ->
output.apply {
appendLine("### [\uD83D\uDCE6 `$pkg`](https://play.google.com/store/apps/details?id=$pkg)")
appendLine("<details>\n")
appendLine(tableHeader)
patches.sortedBy { it.name }.forEach { patch ->
val supportedVersionArray =
patch.compatiblePackages?.lastOrNull()?.second
val supportedVersion =
if (supportedVersionArray?.isNotEmpty() == true) {
val minVersion = supportedVersionArray.elementAt(0)
val maxVersion =
supportedVersionArray.elementAt(supportedVersionArray.size - 1)
if (minVersion == maxVersion)
maxVersion
else
"$minVersion ~ $maxVersion"
} else if (exception.containsKey(pkg))
exception[pkg] + "+"
else
"ALL"
appendLine(
"| `${patch.name}` " +
"| ${patch.description} " +
"| $supportedVersion |"
)
}
appendLine("</details>\n")
}
}
// copy the contents of the temp file to 'README.md'
StringBuilder(readMeFile.readText())
.replace(Regex("\\{\\{\\s?table\\s?}}"), output.toString())
.let(readMeFile::writeText)
}
}
}

View File

@ -0,0 +1,70 @@
package app.revanced.patches.all.misc.versioncode
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.util.getNode
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
private const val MAX_VALUE = Int.MAX_VALUE.toString()
@Suppress("unused")
val changeVersionCodePatch = resourcePatch(
name = "Change version code",
description = "Changes the version code of the app to the value specified in patch options. " +
"Except when mounting, this can prevent app stores from updating the app and allow " +
"the app to be installed over an existing installation that has a higher version code. " +
"By default, the highest version code is set.",
use = false,
) {
val changeVersionCode by booleanOption(
key = "changeVersionCode",
default = false,
title = "Change version code",
description = "Changes the version code of the app.",
required = true
)
val versionCodeOption = stringOption(
key = "versionCode",
default = MAX_VALUE,
values = mapOf(
"Lowest" to "1",
"Highest" to MAX_VALUE,
),
title = "Version code",
description = "The version code to use. (1 ~ $MAX_VALUE)",
required = true,
)
execute {
if (changeVersionCode == false) {
println("INFO: Version code will remain unchanged as 'ChangeVersionCode' is false.")
return@execute
}
fun throwVersionCodeException(versionCodeString: String): PatchException =
PatchException(
"Invalid versionCode: $versionCodeString, " +
"Version code should be larger than 1 and smaller than $MAX_VALUE."
)
val versionCodeString = versionCodeOption.valueOrThrow()
val versionCode: Int
try {
versionCode = Integer.parseInt(versionCodeString)
} catch (e: NumberFormatException) {
throw throwVersionCodeException(versionCodeString)
}
if (versionCode < 1) {
throw throwVersionCodeException(versionCodeString)
}
document("AndroidManifest.xml").use { document ->
val manifestElement = document.getNode("manifest") as Element
manifestElement.setAttribute("android:versionCode", "$versionCode")
}
}
}

View File

@ -0,0 +1,160 @@
package app.revanced.patches.music.account.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.ACCOUNT_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.HIDE_ACCOUNT_COMPONENTS
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Suppress("unused")
val accountComponentsPatch = bytecodePatch(
HIDE_ACCOUNT_COMPONENTS.title,
HIDE_ACCOUNT_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
sharedResourceIdPatch,
settingsPatch,
)
execute {
// region patch for hide account menu
menuEntryFingerprint.methodOrThrow().apply {
val textIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setText"
}
val viewIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "addView"
}
val textRegister = getInstruction<FiveRegisterInstruction>(textIndex).registerD
val viewRegister = getInstruction<FiveRegisterInstruction>(viewIndex).registerD
addInstruction(
textIndex + 1,
"invoke-static {v$textRegister, v$viewRegister}, " +
"$ACCOUNT_CLASS_DESCRIPTOR->hideAccountMenu(Ljava/lang/CharSequence;Landroid/view/View;)V"
)
}
// endregion
// region patch for hide handle
// account menu
accountSwitcherAccessibilityLabelFingerprint.methodOrThrow().apply {
val textColorIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setTextColor"
}
val setVisibilityIndex = indexOfFirstInstructionOrThrow(textColorIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setVisibility"
}
val textViewInstruction =
getInstruction<FiveRegisterInstruction>(setVisibilityIndex)
replaceInstruction(
setVisibilityIndex,
"invoke-static {v${textViewInstruction.registerC}, v${textViewInstruction.registerD}}, " +
"$ACCOUNT_CLASS_DESCRIPTOR->hideHandle(Landroid/widget/TextView;I)V"
)
}
// account switcher
namesInactiveAccountThumbnailSizeFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.startIndex
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex, """
invoke-static {v$targetRegister}, $ACCOUNT_CLASS_DESCRIPTOR->hideHandle(Z)Z
move-result v$targetRegister
"""
)
}
}
// endregion
// region patch for hide terms container
termsOfServiceFingerprint.methodOrThrow().apply {
val insertIndex = indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.name == "setVisibility" &&
reference.definingClass.endsWith("/PrivacyTosFooter;")
}
val visibilityRegister =
getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstruction(
insertIndex + 1,
"const/4 v$visibilityRegister, 0x0"
)
addInstructions(
insertIndex, """
invoke-static {}, $ACCOUNT_CLASS_DESCRIPTOR->hideTermsContainer()I
move-result v$visibilityRegister
"""
)
}
// endregion
addSwitchPreference(
CategoryType.ACCOUNT,
"revanced_hide_account_menu",
"false"
)
addPreferenceWithIntent(
CategoryType.ACCOUNT,
"revanced_hide_account_menu_filter_strings",
"revanced_hide_account_menu"
)
addSwitchPreference(
CategoryType.ACCOUNT,
"revanced_hide_account_menu_empty_component",
"false",
"revanced_hide_account_menu"
)
addSwitchPreference(
CategoryType.ACCOUNT,
"revanced_hide_handle",
"true"
)
addSwitchPreference(
CategoryType.ACCOUNT,
"revanced_hide_terms_container",
"false"
)
updatePatchStatus(HIDE_ACCOUNT_COMPONENTS)
}
}

View File

@ -0,0 +1,47 @@
package app.revanced.patches.music.account.components
import app.revanced.patches.music.utils.resourceid.accountSwitcherAccessibility
import app.revanced.patches.music.utils.resourceid.menuEntry
import app.revanced.patches.music.utils.resourceid.namesInactiveAccountThumbnailSize
import app.revanced.patches.music.utils.resourceid.tosFooter
import app.revanced.util.fingerprint.legacyFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val accountSwitcherAccessibilityLabelFingerprint = legacyFingerprint(
name = "accountSwitcherAccessibilityLabelFingerprint",
returnType = "V",
parameters = listOf("L", "Ljava/lang/Object;"),
literals = listOf(accountSwitcherAccessibility)
)
internal val menuEntryFingerprint = legacyFingerprint(
name = "menuEntryFingerprint",
returnType = "V",
literals = listOf(menuEntry)
)
internal val namesInactiveAccountThumbnailSizeFingerprint = legacyFingerprint(
name = "namesInactiveAccountThumbnailSizeFingerprint",
returnType = "V",
parameters = listOf("L", "Ljava/lang/Object;"),
opcodes = listOf(
Opcode.IF_NEZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.GOTO,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_EQZ
),
literals = listOf(namesInactiveAccountThumbnailSize)
)
internal val termsOfServiceFingerprint = legacyFingerprint(
name = "termsOfServiceFingerprint",
returnType = "Landroid/view/View;",
literals = listOf(tosFooter)
)

View File

@ -0,0 +1,193 @@
package app.revanced.patches.music.actionbar.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.ACTIONBAR_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.HIDE_ACTION_BAR_COMPONENTS
import app.revanced.patches.music.utils.resourceid.likeDislikeContainer
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import kotlin.math.min
@Suppress("unused")
val actionBarComponentsPatch = bytecodePatch(
HIDE_ACTION_BAR_COMPONENTS.title,
HIDE_ACTION_BAR_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
sharedResourceIdPatch,
videoInformationPatch,
)
execute {
actionBarComponentFingerprint.matchOrThrow().let {
it.method.apply {
// hook download button
val addViewIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "addView"
}
val addViewRegister =
getInstruction<FiveRegisterInstruction>(addViewIndex).registerD
addInstruction(
addViewIndex + 1,
"invoke-static {v$addViewRegister}, $ACTIONBAR_CLASS_DESCRIPTOR->inAppDownloadButtonOnClick(Landroid/view/View;)V"
)
// hide action button label
val noLabelIndex = indexOfFirstInstructionOrThrow {
val reference = (this as? ReferenceInstruction)?.reference.toString()
opcode == Opcode.INVOKE_DIRECT &&
reference.endsWith("<init>(Landroid/content/Context;)V") &&
!reference.contains("Lcom/google/android/libraries/youtube/common/ui/YouTubeButton;")
} - 2
val replaceIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT &&
(this as? ReferenceInstruction)?.reference.toString()
.endsWith("Lcom/google/android/libraries/youtube/common/ui/YouTubeButton;-><init>(Landroid/content/Context;)V")
} - 2
val replaceInstruction = getInstruction<TwoRegisterInstruction>(replaceIndex)
val replaceReference = getInstruction<ReferenceInstruction>(replaceIndex).reference
addInstructionsWithLabels(
replaceIndex + 1, """
invoke-static {}, $ACTIONBAR_CLASS_DESCRIPTOR->hideActionBarLabel()Z
move-result v${replaceInstruction.registerA}
if-nez v${replaceInstruction.registerA}, :hidden
iget-object v${replaceInstruction.registerA}, v${replaceInstruction.registerB}, $replaceReference
""", ExternalLabel("hidden", getInstruction(noLabelIndex))
)
removeInstruction(replaceIndex)
// hide action button
val hasNextIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_INTERFACE &&
getReference<MethodReference>()?.name == "hasNext"
}
val freeRegister = min(implementation!!.registerCount - parameters.size - 2, 15)
val spannedIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.returnType == "Landroid/text/Spanned;"
}
val spannedRegister =
getInstruction<FiveRegisterInstruction>(spannedIndex).registerC
val spannedReference = getInstruction<ReferenceInstruction>(spannedIndex).reference
addInstructionsWithLabels(
spannedIndex + 1, """
invoke-static {}, $ACTIONBAR_CLASS_DESCRIPTOR->hideActionButton()Z
move-result v$freeRegister
if-nez v$freeRegister, :hidden
invoke-static {v$spannedRegister}, $spannedReference
""", ExternalLabel("hidden", getInstruction(hasNextIndex))
)
removeInstruction(spannedIndex)
// set action button identifier
val buttonTypeDownloadIndex = it.patternMatch!!.startIndex + 1
val buttonTypeDownloadRegister =
getInstruction<OneRegisterInstruction>(buttonTypeDownloadIndex).registerA
val buttonTypeIndex = it.patternMatch!!.endIndex - 1
val buttonTypeRegister =
getInstruction<OneRegisterInstruction>(buttonTypeIndex).registerA
addInstruction(
buttonTypeIndex + 2,
"invoke-static {v$buttonTypeRegister}, $ACTIONBAR_CLASS_DESCRIPTOR->setButtonType(Ljava/lang/Object;)V"
)
addInstruction(
buttonTypeDownloadIndex,
"invoke-static {v$buttonTypeDownloadRegister}, $ACTIONBAR_CLASS_DESCRIPTOR->setButtonTypeDownload(I)V"
)
}
}
likeDislikeContainerFingerprint.methodOrThrow().apply {
val insertIndex =
indexOfFirstLiteralInstructionOrThrow(likeDislikeContainer) + 2
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex + 1,
"invoke-static {v$insertRegister}, $ACTIONBAR_CLASS_DESCRIPTOR->hideLikeDislikeButton(Landroid/view/View;)V"
)
}
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_like_dislike",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_comment",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_add_to_playlist",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_download",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_share",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_radio",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_hide_action_button_label",
"false"
)
addSwitchPreference(
CategoryType.ACTION_BAR,
"revanced_external_downloader_action",
"false"
)
addPreferenceWithIntent(
CategoryType.ACTION_BAR,
"revanced_external_downloader_package_name",
"revanced_external_downloader_action"
)
updatePatchStatus(HIDE_ACTION_BAR_COMPONENTS)
}
}

View File

@ -0,0 +1,30 @@
package app.revanced.patches.music.actionbar.components
import app.revanced.patches.music.utils.resourceid.likeDislikeContainer
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val actionBarComponentFingerprint = legacyFingerprint(
name = "actionBarComponentFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L"),
opcodes = listOf(
Opcode.AND_INT_LIT16,
Opcode.IF_EQZ,
Opcode.IGET_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.SGET_OBJECT
),
literals = listOf(99180L),
)
internal val likeDislikeContainerFingerprint = legacyFingerprint(
name = "likeDislikeContainerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(likeDislikeContainer)
)

View File

@ -0,0 +1,190 @@
package app.revanced.patches.music.ads.general
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.navigation.components.navigationBarComponentsPatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.ADS_PATH
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.patch.PatchList.HIDE_ADS
import app.revanced.patches.music.utils.resourceid.buttonContainer
import app.revanced.patches.music.utils.resourceid.floatingLayout
import app.revanced.patches.music.utils.resourceid.interstitialsContainer
import app.revanced.patches.music.utils.resourceid.privacyTosFooter
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.ads.baseAdsPatch
import app.revanced.patches.shared.ads.hookLithoFullscreenAds
import app.revanced.patches.shared.ads.hookNonLithoFullscreenAds
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val ADS_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/AdsFilter;"
private const val PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR =
"$ADS_PATH/PremiumPromotionPatch;"
private const val PREMIUM_PROMOTION_BANNER_CLASS_DESCRIPTOR =
"$ADS_PATH/PremiumRenewalPatch;"
@Suppress("unused")
val adsPatch = bytecodePatch(
HIDE_ADS.title,
HIDE_ADS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseAdsPatch("$ADS_PATH/MusicAdsPatch;", "hideMusicAds"),
lithoFilterPatch,
navigationBarComponentsPatch, // for 'Hide upgrade button' setting
sharedResourceIdPatch,
settingsPatch,
)
execute {
// region patch for hide fullscreen ads
// non-litho view, used in some old clients
interstitialsContainerFingerprint
.methodOrThrow()
.hookNonLithoFullscreenAds(interstitialsContainer)
// litho view, used in 'ShowDialogCommandOuterClass' in innertube
showDialogCommandFingerprint
.matchOrThrow()
.hookLithoFullscreenAds()
// endregion
// region patch for hide premium promotion popup
floatingLayoutFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstLiteralInstructionOrThrow(floatingLayout) + 2
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $PREMIUM_PROMOTION_POP_UP_CLASS_DESCRIPTOR->hidePremiumPromotion(Landroid/view/View;)V"
)
}
// endregion
// region patch for hide premium renewal banner
notifierShelfFingerprint.methodOrThrow().apply {
val linearLayoutIndex =
indexOfFirstLiteralInstructionOrThrow(buttonContainer) + 3
val linearLayoutRegister =
getInstruction<OneRegisterInstruction>(linearLayoutIndex).registerA
addInstruction(
linearLayoutIndex + 1,
"invoke-static {v$linearLayoutRegister}, $PREMIUM_PROMOTION_BANNER_CLASS_DESCRIPTOR->hidePremiumRenewal(Landroid/widget/LinearLayout;)V"
)
}
// endregion
// region patch for hide get premium
// get premium button at the top of the account switching menu
getPremiumTextViewFingerprint.matchOrThrow().let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex + 1,
"const/4 v$register, 0x0"
)
}
}
// get premium button at the bottom of the account switching menu
accountMenuFooterFingerprint.methodOrThrow().apply {
val constIndex =
indexOfFirstLiteralInstructionOrThrow(privacyTosFooter)
val walkerIndex =
indexOfFirstInstructionOrThrow(constIndex + 2, Opcode.INVOKE_VIRTUAL)
val viewIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.IGET_OBJECT)
val viewReference =
getInstruction<ReferenceInstruction>(viewIndex).reference.toString()
val walkerMethod = getWalkerMethod(walkerIndex)
walkerMethod.apply {
val insertIndex = indexOfFirstInstructionOrThrow {
getReference<FieldReference>()?.toString() == viewReference
}
val nullCheckIndex =
indexOfFirstInstructionOrThrow(insertIndex - 1, Opcode.IF_NEZ)
val nullCheckRegister =
getInstruction<OneRegisterInstruction>(nullCheckIndex).registerA
addInstruction(
nullCheckIndex,
"const/4 v$nullCheckRegister, 0x0"
)
}
}
addLithoFilter(ADS_FILTER_CLASS_DESCRIPTOR)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_fullscreen_ads",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_general_ads",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_music_ads",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_paid_promotion_label",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_premium_promotion",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_premium_renewal",
"true"
)
addSwitchPreference(
CategoryType.ADS,
"revanced_hide_promotion_alert_banner",
"true"
)
updatePatchStatus(HIDE_ADS)
}
}

View File

@ -0,0 +1,87 @@
package app.revanced.patches.music.ads.general
import app.revanced.patches.music.utils.resourceid.buttonContainer
import app.revanced.patches.music.utils.resourceid.floatingLayout
import app.revanced.patches.music.utils.resourceid.interstitialsContainer
import app.revanced.patches.music.utils.resourceid.musicNotifierShelf
import app.revanced.patches.music.utils.resourceid.privacyTosFooter
import app.revanced.patches.music.utils.resourceid.slidingDialogAnimation
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val accountMenuFooterFingerprint = legacyFingerprint(
name = "accountMenuFooterFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET_OBJECT
),
literals = listOf(privacyTosFooter)
)
internal val floatingLayoutFingerprint = legacyFingerprint(
name = "floatingLayoutFingerprint",
returnType = "Landroid/view/View;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(floatingLayout)
)
internal val getPremiumTextViewFingerprint = legacyFingerprint(
name = "getPremiumTextViewFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.IGET_BOOLEAN,
Opcode.CONST_4,
Opcode.IF_EQZ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_STATIC
),
strings = listOf("FEmusic_history")
)
internal val interstitialsContainerFingerprint = legacyFingerprint(
name = "interstitialsContainerFingerprint",
returnType = "V",
strings = listOf("overlay_controller_param"),
literals = listOf(interstitialsContainer)
)
internal val notifierShelfFingerprint = legacyFingerprint(
name = "notifierShelfFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(musicNotifierShelf, buttonContainer)
)
internal val showDialogCommandFingerprint = legacyFingerprint(
name = "showDialogCommandFingerprint",
returnType = "V",
opcodes = listOf(
Opcode.IF_EQ,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET, // get dialog code
),
literals = listOf(slidingDialogAnimation),
// 6.26 and earlier has a different first parameter.
// Since this fingerprint is somewhat weak, work around by checking for both method parameter signatures.
customFingerprint = custom@{ method, _ ->
// 6.26 and earlier parameters are: "L", "L"
// 6.27+ parameters are "[B", "L"
val parameterTypes = method.parameterTypes
parameterTypes.size == 2 && parameterTypes[1].startsWith("L")
},
)

View File

@ -0,0 +1,78 @@
package app.revanced.patches.music.flyoutmenu.components
import app.revanced.patches.music.utils.resourceid.endButtonsContainer
import app.revanced.patches.music.utils.resourceid.touchOutside
import app.revanced.patches.music.utils.resourceid.trimSilenceSwitch
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val endButtonsContainerFingerprint = legacyFingerprint(
name = "endButtonsContainerFingerprint",
returnType = "V",
literals = listOf(endButtonsContainer)
)
internal val menuItemFingerprint = legacyFingerprint(
name = "menuItemFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.INVOKE_DIRECT,
Opcode.MOVE_RESULT_OBJECT
),
strings = listOf("toggleMenuItemMutations")
)
internal val screenWidthFingerprint = legacyFingerprint(
name = "screenWidthFingerprint",
returnType = "Z",
parameters = listOf("L"),
opcodes = listOf(Opcode.IF_LT),
literals = listOf(600L)
)
internal val screenWidthParentFingerprint = legacyFingerprint(
name = "screenWidthParentFingerprint",
returnType = "Landroid/graphics/Bitmap;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("Landroid/app/Activity;", "I"),
customFingerprint = { method, _ ->
method.indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "destroyDrawingCache"
} >= 0
}
)
internal val sleepTimerFingerprint = legacyFingerprint(
name = "sleepTimerFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45372767L)
)
internal val touchOutsideFingerprint = legacyFingerprint(
name = "touchOutsideFingerprint",
returnType = "Landroid/view/View;",
literals = listOf(touchOutside)
)
internal val trimSilenceConfigFingerprint = legacyFingerprint(
name = "trimSilenceConfigFingerprint",
returnType = "Z",
literals = listOf(45619123L)
)
internal val trimSilenceSwitchFingerprint = legacyFingerprint(
name = "trimSilenceSwitchFingerprint",
returnType = "Landroid/view/View;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(trimSilenceSwitch)
)

View File

@ -0,0 +1,454 @@
package app.revanced.patches.music.flyoutmenu.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.extension.Constants.FLYOUT_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.flyoutmenu.flyoutMenuHookPatch
import app.revanced.patches.music.utils.patch.PatchList.FLYOUT_MENU_COMPONENTS
import app.revanced.patches.music.utils.playservice.is_6_36_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.endButtonsContainer
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.resourceid.trimSilenceSwitch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.utils.videotype.videoTypeHookPatch
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.resolvable
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private val resourceFileArray = arrayOf(
"yt_outline_play_arrow_half_circle_black_24"
).map { "$it.png" }.toTypedArray()
private val flyoutMenuComponentsResourcePatch = resourcePatch(
description = "flyoutMenuComponentsResourcePatch"
) {
execute {
arrayOf("xxxhdpi", "xxhdpi", "xhdpi", "hdpi", "mdpi")
.map { "drawable-$it" }
.map { directory ->
ResourceGroup(
directory, *resourceFileArray
)
}
.let { resourceGroups ->
resourceGroups.forEach {
copyResources("music/flyout", it)
}
}
}
}
private const val FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/PlayerFlyoutMenuFilter;"
@Suppress("unused")
val flyoutMenuComponentsPatch = bytecodePatch(
FLYOUT_MENU_COMPONENTS.title,
FLYOUT_MENU_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
flyoutMenuComponentsResourcePatch,
flyoutMenuHookPatch,
lithoFilterPatch,
sharedResourceIdPatch,
settingsPatch,
versionCheckPatch,
videoInformationPatch,
videoTypeHookPatch,
)
execute {
var trimSilenceIncluded = false
// region patch for enable compact dialog
screenWidthFingerprint.matchOrThrow(screenWidthParentFingerprint).let {
it.method.apply {
val index = it.patternMatch!!.startIndex
val register = getInstruction<TwoRegisterInstruction>(index).registerA
addInstructions(
index, """
invoke-static {v$register}, $FLYOUT_CLASS_DESCRIPTOR->enableCompactDialog(I)I
move-result v$register
"""
)
}
}
// endregion
// region patch for enable trim silence
if (trimSilenceConfigFingerprint.resolvable()) {
trimSilenceConfigFingerprint.injectLiteralInstructionBooleanCall(
45619123L,
"$FLYOUT_CLASS_DESCRIPTOR->enableTrimSilence(Z)Z"
)
trimSilenceSwitchFingerprint.methodOrThrow().apply {
val constIndex =
indexOfFirstLiteralInstructionOrThrow(trimSilenceSwitch)
val onCheckedChangedListenerIndex =
indexOfFirstInstructionOrThrow(constIndex, Opcode.INVOKE_DIRECT)
val onCheckedChangedListenerReference =
getInstruction<ReferenceInstruction>(onCheckedChangedListenerIndex).reference
val onCheckedChangedListenerDefiningClass =
(onCheckedChangedListenerReference as MethodReference).definingClass
findMethodOrThrow(onCheckedChangedListenerDefiningClass) {
name == "onCheckedChanged"
}.apply {
val onCheckedChangedWalkerIndex =
indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "V" &&
reference.parameterTypes.size == 1 &&
reference.parameterTypes[0] == "Z"
}
getWalkerMethod(onCheckedChangedWalkerIndex).apply {
val insertIndex = indexOfFirstInstructionOrThrow(Opcode.MOVE_RESULT)
val insertRegister =
getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstructions(
insertIndex + 1, """
invoke-static {v$insertRegister}, $FLYOUT_CLASS_DESCRIPTOR->enableTrimSilenceSwitch(Z)Z
move-result v$insertRegister
"""
)
}
}
}
trimSilenceIncluded = true
}
// endregion
// region patch for hide flyout menu components and replace menu
menuItemFingerprint.matchOrThrow().let {
it.method.apply {
val freeIndex = indexOfFirstInstructionOrThrow(Opcode.OR_INT_LIT16)
val textViewIndex = it.patternMatch!!.startIndex
val imageViewIndex = it.patternMatch!!.endIndex
val freeRegister =
getInstruction<TwoRegisterInstruction>(freeIndex).registerA
val textViewRegister =
getInstruction<OneRegisterInstruction>(textViewIndex).registerA
val imageViewRegister =
getInstruction<OneRegisterInstruction>(imageViewIndex).registerA
val enumIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC
&& (this as? ReferenceInstruction)?.reference.toString()
.contains("(I)L")
} + 1
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
addInstructionsWithLabels(
enumIndex + 1,
"""
invoke-static {v$enumRegister, v$textViewRegister, v$imageViewRegister}, $FLYOUT_CLASS_DESCRIPTOR->replaceComponents(Ljava/lang/Enum;Landroid/widget/TextView;Landroid/widget/ImageView;)V
invoke-static {v$enumRegister}, $FLYOUT_CLASS_DESCRIPTOR->hideComponents(Ljava/lang/Enum;)Z
move-result v$freeRegister
if-nez v$freeRegister, :hide
""",
ExternalLabel("hide", getInstruction(implementation!!.instructions.lastIndex))
)
}
}
touchOutsideFingerprint.methodOrThrow().apply {
val setOnClickListenerIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setOnClickListener"
}
val setOnClickListenerRegister =
getInstruction<FiveRegisterInstruction>(setOnClickListenerIndex).registerC
addInstruction(
setOnClickListenerIndex + 1,
"invoke-static {v$setOnClickListenerRegister}, $FLYOUT_CLASS_DESCRIPTOR->setTouchOutSideView(Landroid/view/View;)V"
)
}
endButtonsContainerFingerprint.methodOrThrow().apply {
val startIndex =
indexOfFirstLiteralInstructionOrThrow(endButtonsContainer)
val targetIndex =
indexOfFirstInstructionOrThrow(startIndex, Opcode.MOVE_RESULT_OBJECT)
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $FLYOUT_CLASS_DESCRIPTOR->hideLikeDislikeContainer(Landroid/view/View;)V"
)
}
// endregion
// region patch for enable sleep timer
/**
* Forces sleep timer menu to be enabled.
* This method may be desperate in the future.
*/
if (sleepTimerFingerprint.resolvable()) {
sleepTimerFingerprint.methodOrThrow().apply {
val insertIndex = implementation!!.instructions.lastIndex
val targetRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex,
"const/4 v$targetRegister, 0x1"
)
}
}
// endregion
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_enable_compact_dialog",
"true"
)
if (trimSilenceIncluded) {
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_enable_trim_silence",
"false"
)
}
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_like_dislike",
"false",
false
)
if (is_6_36_or_greater) {
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_3_column_component",
"false",
false
)
}
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_add_to_queue",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_captions",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_delete_playlist",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_dismiss_queue",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_download",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_edit_playlist",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_go_to_album",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_go_to_artist",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_go_to_episode",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_go_to_podcast",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_help",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_play_next",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_quality",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_remove_from_library",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_remove_from_playlist",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_report",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_save_episode_for_later",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_save_to_library",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_save_to_playlist",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_share",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_shuffle_play",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_sleep_timer",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_start_radio",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_stats_for_nerds",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_subscribe",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_hide_flyout_menu_view_song_credit",
"false",
false
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_replace_flyout_menu_dismiss_queue",
"false"
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_replace_flyout_menu_dismiss_queue_continue_watch",
"true",
"revanced_replace_flyout_menu_dismiss_queue"
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_replace_flyout_menu_report",
"true"
)
addSwitchPreference(
CategoryType.FLYOUT,
"revanced_replace_flyout_menu_report_only_player",
"true",
"revanced_replace_flyout_menu_report"
)
updatePatchStatus(FLYOUT_MENU_COMPONENTS)
}
}

View File

@ -0,0 +1,46 @@
package app.revanced.patches.music.general.amoled
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.music.utils.patch.PatchList.AMOLED
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.drawable.addDrawableColorHook
import app.revanced.patches.shared.drawable.drawableColorHookPatch
import org.w3c.dom.Element
@Suppress("unused")
val amoledPatch = resourcePatch(
AMOLED.title,
AMOLED.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
drawableColorHookPatch,
settingsPatch
)
execute {
addDrawableColorHook("$UTILS_PATH/DrawableColorPatch;->getColor(I)I")
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")) {
"yt_black0", "yt_black1", "yt_black1_opacity95", "yt_black1_opacity98", "yt_black2", "yt_black3",
"yt_black4", "yt_status_bar_background_dark", "ytm_color_grey_12", "material_grey_850" -> "@android:color/black"
else -> continue
}
}
}
updatePatchStatus(AMOLED)
}
}

View File

@ -0,0 +1,34 @@
package app.revanced.patches.music.general.autocaptions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_AUTO_CAPTIONS
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.captions.baseAutoCaptionsPatch
@Suppress("unused")
val autoCaptionsPatch = bytecodePatch(
DISABLE_AUTO_CAPTIONS.title,
DISABLE_AUTO_CAPTIONS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseAutoCaptionsPatch,
settingsPatch
)
execute {
addSwitchPreference(
CategoryType.GENERAL,
"revanced_disable_auto_captions",
"false"
)
updatePatchStatus(DISABLE_AUTO_CAPTIONS)
}
}

View File

@ -0,0 +1,177 @@
package app.revanced.patches.music.general.components
import app.revanced.patches.music.utils.resourceid.chipCloud
import app.revanced.patches.music.utils.resourceid.historyMenuItem
import app.revanced.patches.music.utils.resourceid.musicTasteBuilderShelf
import app.revanced.patches.music.utils.resourceid.offlineSettingsMenuItem
import app.revanced.patches.music.utils.resourceid.playerOverlayChip
import app.revanced.patches.music.utils.resourceid.toolTipContentView
import app.revanced.patches.music.utils.resourceid.topBarMenuItemImageView
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val chipCloudFingerprint = legacyFingerprint(
name = "chipCloudFingerprint",
returnType = "V",
opcodes = listOf(
Opcode.CONST,
Opcode.CONST_4,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT
),
literals = listOf(chipCloud),
)
internal val contentPillFingerprint = legacyFingerprint(
name = "contentPillFingerprint",
returnType = "V",
strings = listOf("Content pill VE is null")
)
internal val floatingButtonFingerprint = legacyFingerprint(
name = "floatingButtonFingerprint",
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(Opcode.AND_INT_LIT16)
)
internal val floatingButtonParentFingerprint = legacyFingerprint(
name = "floatingButtonParentFingerprint",
returnType = "V",
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(Opcode.INVOKE_DIRECT),
literals = listOf(259982244L),
)
internal val historyMenuItemFingerprint = legacyFingerprint(
name = "historyMenuItemFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/Menu;"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.RETURN_VOID
),
literals = listOf(historyMenuItem),
customFingerprint = { _, classDef ->
classDef.methods.count() == 5
}
)
internal val historyMenuItemOfflineTabFingerprint = legacyFingerprint(
name = "historyMenuItemOfflineTabFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/Menu;"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.RETURN_VOID
),
literals = listOf(historyMenuItem, offlineSettingsMenuItem),
)
internal val mediaRouteButtonFingerprint = legacyFingerprint(
name = "mediaRouteButtonFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
strings = listOf("MediaRouteButton")
)
internal val parentToolMenuFingerprint = legacyFingerprint(
name = "parentToolMenuFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.CONST_4,
Opcode.INVOKE_VIRTUAL,
Opcode.IGET,
),
strings = listOf("pref_key_parent_tools"),
customFingerprint = { method, _ ->
method.name == "onSettingsLoaded"
}
)
internal val playerOverlayChipFingerprint = legacyFingerprint(
name = "playerOverlayChipFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(playerOverlayChip),
)
internal val preferenceScreenFingerprint = legacyFingerprint(
name = "preferenceScreenFingerprint",
returnType = "V",
customFingerprint = { method, _ ->
method.definingClass == "Lcom/google/android/apps/youtube/music/settings/fragment/SettingsHeadersFragment;" &&
method.name == "onCreatePreferences"
}
)
internal val searchBarFingerprint = legacyFingerprint(
name = "searchBarFingerprint",
returnType = "V",
customFingerprint = { method, _ ->
indexOfVisibilityInstruction(method) >= 0
}
)
fun indexOfVisibilityInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setVisibility"
}
internal val searchBarParentFingerprint = legacyFingerprint(
name = "searchBarParentFingerprint",
returnType = "Landroid/content/Intent;",
strings = listOf("web_search")
)
internal val soundSearchFingerprint = legacyFingerprint(
name = "soundSearchFingerprint",
parameters = emptyList(),
literals = listOf(45625491L),
)
internal val tasteBuilderConstructorFingerprint = legacyFingerprint(
name = "tasteBuilderConstructorFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(musicTasteBuilderShelf),
)
internal val tasteBuilderSyntheticFingerprint = legacyFingerprint(
name = "tasteBuilderSyntheticFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL or AccessFlags.SYNTHETIC,
parameters = listOf("L", "Ljava/lang/Object;"),
opcodes = listOf(
Opcode.IF_NEZ,
Opcode.IGET_OBJECT
)
)
internal val tooltipContentViewFingerprint = legacyFingerprint(
name = "tooltipContentViewFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
literals = listOf(toolTipContentView),
)
internal val topBarMenuItemImageViewFingerprint = legacyFingerprint(
name = "topBarMenuItemImageViewFingerprint",
returnType = "Landroid/view/View;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(topBarMenuItemImageView),
)

View File

@ -0,0 +1,426 @@
package app.revanced.patches.music.general.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.GENERAL_PATH
import app.revanced.patches.music.utils.patch.PatchList.HIDE_LAYOUT_COMPONENTS
import app.revanced.patches.music.utils.playservice.is_6_42_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.musicTasteBuilderShelf
import app.revanced.patches.music.utils.resourceid.playerOverlayChip
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.resourceid.topBarMenuItemImageView
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.patches.shared.settingmenu.settingsMenuPatch
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.fingerprint.resolvable
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_SETTINGS_MENU_DESCRIPTOR =
"$GENERAL_PATH/SettingsMenuPatch;"
private const val CUSTOM_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/CustomFilter;"
private const val LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/LayoutComponentsFilter;"
@Suppress("unused")
val layoutComponentsPatch = bytecodePatch(
HIDE_LAYOUT_COMPONENTS.title,
HIDE_LAYOUT_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
lithoFilterPatch,
sharedResourceIdPatch,
settingsPatch,
settingsMenuPatch,
versionCheckPatch,
)
execute {
var notificationButtonIncluded = false
var soundSearchButtonIncluded = false
// region patch for hide cast button
// hide cast button
mediaRouteButtonFingerprint.mutableClassOrThrow().let {
val setVisibilityMethod =
it.methods.find { method -> method.name == "setVisibility" }
setVisibilityMethod?.addInstructions(
0, """
invoke-static {p1}, $GENERAL_CLASS_DESCRIPTOR->hideCastButton(I)I
move-result p1
"""
) ?: throw PatchException("Failed to find setVisibility method")
}
// hide floating cast banner
playerOverlayChipFingerprint.methodOrThrow().apply {
val targetIndex =
indexOfFirstLiteralInstructionOrThrow(playerOverlayChip) + 2
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->hideCastButton(Landroid/view/View;)V"
)
}
// endregion
// region patch for hide category bar
chipCloudFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.endIndex
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static { v$targetRegister }, $GENERAL_CLASS_DESCRIPTOR->hideCategoryBar(Landroid/view/View;)V"
)
}
}
// endregion
// region patch for hide floating button
floatingButtonFingerprint.methodOrThrow(floatingButtonParentFingerprint).apply {
addInstructionsWithLabels(
1, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->hideFloatingButton()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(1))
)
}
// endregion
// region patch for hide history button
setOf(
historyMenuItemFingerprint,
historyMenuItemOfflineTabFingerprint
).forEach { fingerprint ->
fingerprint.matchOrThrow().let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex
val insertRegister =
getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstructions(
insertIndex, """
invoke-static {v$insertRegister}, $GENERAL_CLASS_DESCRIPTOR->hideHistoryButton(Z)Z
move-result v$insertRegister
"""
)
}
}
}
// endregion
// region patch for hide notification button
if (is_6_42_or_greater) {
topBarMenuItemImageViewFingerprint.methodOrThrow().apply {
val constIndex =
indexOfFirstLiteralInstructionOrThrow(topBarMenuItemImageView)
val targetIndex =
indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT)
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->hideNotificationButton(Landroid/view/View;)V"
)
}
notificationButtonIncluded = true
}
// endregion
// region patch for hide setting menus
preferenceScreenFingerprint.methodOrThrow().apply {
addInstructions(
implementation!!.instructions.lastIndex, """
invoke-virtual/range {p0 .. p0}, Lcom/google/android/apps/youtube/music/settings/fragment/SettingsHeadersFragment;->getPreferenceScreen()Landroidx/preference/PreferenceScreen;
move-result-object v0
invoke-static {v0}, $EXTENSION_SETTINGS_MENU_DESCRIPTOR->hideSettingsMenu(Landroidx/preference/PreferenceScreen;)V
"""
)
}
// The lowest version supported by the patch does not have parent tool settings
if (parentToolMenuFingerprint.resolvable()) {
parentToolMenuFingerprint.matchOrThrow().let {
it.method.apply {
val index = it.patternMatch!!.startIndex + 1
val register = getInstruction<FiveRegisterInstruction>(index).registerD
addInstructions(
index, """
invoke-static {v$register}, $EXTENSION_SETTINGS_MENU_DESCRIPTOR->hideParentToolsMenu(Z)Z
move-result v$register
"""
)
}
}
}
// endregion
// region patch for hide sound search button
if (soundSearchFingerprint.resolvable()) {
soundSearchFingerprint.injectLiteralInstructionBooleanCall(
45625491L,
"$GENERAL_CLASS_DESCRIPTOR->hideSoundSearchButton(Z)Z"
)
soundSearchButtonIncluded = true
}
// endregion
// region patch for hide tap to update button
contentPillFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0,
"""
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->hideTapToUpdateButton()Z
move-result v0
if-eqz v0, :show
return-void
""", ExternalLabel("show", getInstruction(0))
)
}
// endregion
// region patch for hide taste builder
tasteBuilderConstructorFingerprint.methodOrThrow().apply {
val constIndex =
indexOfFirstLiteralInstructionOrThrow(musicTasteBuilderShelf)
val targetIndex =
indexOfFirstInstructionOrThrow(constIndex, Opcode.MOVE_RESULT_OBJECT)
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->hideTasteBuilder(Landroid/view/View;)V"
)
}
tasteBuilderSyntheticFingerprint.matchOrThrow(tasteBuilderConstructorFingerprint).let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex
val insertRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex,
"const/4 v$insertRegister, 0x0"
)
}
}
// endregion
// region patch for hide tooltip content
tooltipContentViewFingerprint.methodOrThrow().addInstruction(
0,
"return-void"
)
// endregion
// region patch for hide voice search button
searchBarFingerprint.methodOrThrow(searchBarParentFingerprint).apply {
val setVisibilityIndex = indexOfVisibilityInstruction(this)
val setVisibilityInstruction =
getInstruction<FiveRegisterInstruction>(setVisibilityIndex)
replaceInstruction(
setVisibilityIndex,
"invoke-static {v${setVisibilityInstruction.registerC}, v${setVisibilityInstruction.registerD}}, " +
"$GENERAL_CLASS_DESCRIPTOR->hideVoiceSearchButton(Landroid/widget/ImageView;I)V"
)
}
// endregion
addLithoFilter(CUSTOM_FILTER_CLASS_DESCRIPTOR)
addLithoFilter(LAYOUT_COMPONENTS_FILTER_CLASS_DESCRIPTOR)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_custom_filter",
"false"
)
addPreferenceWithIntent(
CategoryType.GENERAL,
"revanced_custom_filter_strings",
"revanced_custom_filter"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_button_shelf",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_carousel_shelf",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_playlist_card_shelf",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_samples_shelf",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_cast_button",
"true"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_category_bar",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_floating_button",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_tap_to_update_button",
"false"
)
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_history_button",
"false"
)
if (notificationButtonIncluded) {
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_notification_button",
"false"
)
}
if (soundSearchButtonIncluded) {
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_sound_search_button",
"false"
)
}
addSwitchPreference(
CategoryType.GENERAL,
"revanced_hide_voice_search_button",
"false"
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_parent_tools",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_general",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_playback",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_data_saving",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_downloads_and_storage",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_notification",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_privacy_and_location",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_recommendations",
"false",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_paid_memberships",
"true",
false
)
addSwitchPreference(
CategoryType.SETTINGS,
"revanced_hide_settings_menu_about",
"false",
false
)
updatePatchStatus(HIDE_LAYOUT_COMPONENTS)
}
}

View File

@ -0,0 +1,35 @@
package app.revanced.patches.music.general.dialog
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.REMOVE_VIEWER_DISCRETION_DIALOG
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.dialog.baseViewerDiscretionDialogPatch
@Suppress("unused")
val viewerDiscretionDialogPatch = bytecodePatch(
REMOVE_VIEWER_DISCRETION_DIALOG.title,
REMOVE_VIEWER_DISCRETION_DIALOG.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseViewerDiscretionDialogPatch(GENERAL_CLASS_DESCRIPTOR),
settingsPatch,
)
execute {
addSwitchPreference(
CategoryType.GENERAL,
"revanced_remove_viewer_discretion_dialog",
"false"
)
updatePatchStatus(REMOVE_VIEWER_DISCRETION_DIALOG)
}
}

View File

@ -0,0 +1,21 @@
package app.revanced.patches.music.general.landscapemode
import app.revanced.patches.music.utils.resourceid.isTablet
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val tabletIdentifierFingerprint = legacyFingerprint(
name = "tabletIdentifierFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L"),
opcodes = listOf(
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT
),
literals = listOf(isTablet)
)

View File

@ -0,0 +1,53 @@
package app.revanced.patches.music.general.landscapemode
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.ENABLE_LANDSCAPE_MODE
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
val landScapeModePatch = bytecodePatch(
ENABLE_LANDSCAPE_MODE.title,
ENABLE_LANDSCAPE_MODE.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
sharedResourceIdPatch,
settingsPatch,
)
execute {
tabletIdentifierFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.endIndex
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex + 1, """
invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->enableLandScapeMode(Z)Z
move-result v$targetRegister
"""
)
}
}
addSwitchPreference(
CategoryType.GENERAL,
"revanced_enable_landscape_mode",
"false"
)
updatePatchStatus(ENABLE_LANDSCAPE_MODE)
}
}

View File

@ -0,0 +1,15 @@
package app.revanced.patches.music.general.oldstylelibraryshelf
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val browseIdFingerprint = legacyFingerprint(
name = "browseIdFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
strings = listOf("FEmusic_offline"),
literals = listOf(45358178L),
)

View File

@ -0,0 +1,53 @@
package app.revanced.patches.music.general.oldstylelibraryshelf
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.RESTORE_OLD_STYLE_LIBRARY_SHELF
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstStringInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
@Suppress("unused")
val oldStyleLibraryShelfPatch = bytecodePatch(
RESTORE_OLD_STYLE_LIBRARY_SHELF.title,
RESTORE_OLD_STYLE_LIBRARY_SHELF.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
browseIdFingerprint.methodOrThrow().apply {
val stringIndex = indexOfFirstStringInstructionOrThrow("FEmusic_offline")
val targetIndex =
indexOfFirstInstructionReversedOrThrow(stringIndex, Opcode.IGET_OBJECT)
val targetRegister = getInstruction<TwoRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex + 1, """
invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->restoreOldStyleLibraryShelf(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
"""
)
}
addSwitchPreference(
CategoryType.GENERAL,
"revanced_restore_old_style_library_shelf",
"false"
)
updatePatchStatus(RESTORE_OLD_STYLE_LIBRARY_SHELF)
}
}

View File

@ -0,0 +1,97 @@
package app.revanced.patches.music.general.redirection
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_DISLIKE_REDIRECTION
import app.revanced.patches.music.utils.pendingIntentReceiverFingerprint
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstStringInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.iface.reference.Reference
@Suppress("unused")
val dislikeRedirectionPatch = bytecodePatch(
DISABLE_DISLIKE_REDIRECTION.title,
DISABLE_DISLIKE_REDIRECTION.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
lateinit var onClickReference: Reference
pendingIntentReceiverFingerprint.methodOrThrow().apply {
val startIndex = indexOfFirstStringInstructionOrThrow("YTM Dislike")
val onClickRelayIndex =
indexOfFirstInstructionReversedOrThrow(startIndex, Opcode.INVOKE_VIRTUAL)
val onClickRelayMethod = getWalkerMethod(onClickRelayIndex)
onClickRelayMethod.apply {
val onClickMethodIndex =
indexOfFirstInstructionReversedOrThrow(Opcode.INVOKE_DIRECT)
val onClickMethod = getWalkerMethod(onClickMethodIndex)
onClickMethod.apply {
val onClickIndex = indexOfFirstInstructionOrThrow {
val reference =
((this as? ReferenceInstruction)?.reference as? MethodReference)
opcode == Opcode.INVOKE_INTERFACE &&
reference?.returnType == "V" &&
reference.parameterTypes.size == 1
}
onClickReference =
getInstruction<ReferenceInstruction>(onClickIndex).reference
disableDislikeRedirection(onClickIndex)
}
}
}
dislikeButtonOnClickListenerFingerprint.methodOrThrow().apply {
val onClickIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.toString() == onClickReference.toString()
}
disableDislikeRedirection(onClickIndex)
}
addSwitchPreference(
CategoryType.GENERAL,
"revanced_disable_dislike_redirection",
"false"
)
updatePatchStatus(DISABLE_DISLIKE_REDIRECTION)
}
}
private fun MutableMethod.disableDislikeRedirection(onClickIndex: Int) {
val targetIndex = indexOfFirstInstructionReversedOrThrow(onClickIndex, Opcode.IF_EQZ)
val insertRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructionsWithLabels(
targetIndex + 1, """
invoke-static {}, $GENERAL_CLASS_DESCRIPTOR->disableDislikeRedirection()Z
move-result v$insertRegister
if-nez v$insertRegister, :disable
""", ExternalLabel("disable", getInstruction(onClickIndex + 1))
)
}

View File

@ -0,0 +1,17 @@
package app.revanced.patches.music.general.redirection
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val dislikeButtonOnClickListenerFingerprint = legacyFingerprint(
name = "dislikeButtonOnClickListenerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;"),
literals = listOf(53465L),
customFingerprint = { method, _ ->
method.name == "onClick"
}
)

View File

@ -0,0 +1,83 @@
package app.revanced.patches.music.general.spoofappversion
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.general.oldstylelibraryshelf.oldStyleLibraryShelfPatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.extension.Constants.PATCH_STATUS_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.SPOOF_APP_VERSION
import app.revanced.patches.music.utils.playservice.is_7_18_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.spoof.appversion.baseSpoofAppVersionPatch
import app.revanced.util.appendAppVersion
import app.revanced.util.findMethodOrThrow
private var defaultValue = "false"
private val spoofAppVersionBytecodePatch = bytecodePatch {
dependsOn(
baseSpoofAppVersionPatch("$GENERAL_CLASS_DESCRIPTOR->getVersionOverride(Ljava/lang/String;)Ljava/lang/String;"),
versionCheckPatch,
)
execute {
if (is_7_18_or_greater) {
findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
name == "SpoofAppVersionDefaultString"
}.replaceInstruction(
0,
"const-string v0, \"7.16.53\""
)
findMethodOrThrow(PATCH_STATUS_CLASS_DESCRIPTOR) {
name == "SpoofAppVersionDefaultBoolean"
}.replaceInstruction(
0,
"const/4 v0, 0x1"
)
defaultValue = "true"
}
}
}
@Suppress("unused")
val spoofAppVersionPatch = resourcePatch(
SPOOF_APP_VERSION.title,
SPOOF_APP_VERSION.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
spoofAppVersionBytecodePatch,
oldStyleLibraryShelfPatch,
settingsPatch,
versionCheckPatch,
)
execute {
if (is_7_18_or_greater) {
appendAppVersion("7.16.53")
}
addSwitchPreference(
CategoryType.GENERAL,
"revanced_spoof_app_version",
defaultValue
)
addPreferenceWithIntent(
CategoryType.GENERAL,
"revanced_spoof_app_version_target",
"revanced_spoof_app_version"
)
updatePatchStatus(SPOOF_APP_VERSION)
}
}

View File

@ -0,0 +1,52 @@
package app.revanced.patches.music.general.startpage
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.CHANGE_START_PAGE
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
@Suppress("unused")
val changeStartPagePatch = bytecodePatch(
CHANGE_START_PAGE.title,
CHANGE_START_PAGE.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
coldStartUpFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.endIndex
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex + 1, """
invoke-static {v$targetRegister}, $GENERAL_CLASS_DESCRIPTOR->changeStartPage(Ljava/lang/String;)Ljava/lang/String;
move-result-object v$targetRegister
return-object v$targetRegister
"""
)
removeInstruction(targetIndex)
}
}
addPreferenceWithIntent(
CategoryType.GENERAL,
"revanced_change_start_page"
)
updatePatchStatus(CHANGE_START_PAGE)
}
}

View File

@ -0,0 +1,20 @@
package app.revanced.patches.music.general.startpage
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val coldStartUpFingerprint = legacyFingerprint(
name = "coldStartUpFingerprint",
returnType = "Ljava/lang/String;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.GOTO,
Opcode.CONST_STRING,
Opcode.RETURN_OBJECT
),
strings = listOf("FEmusic_library_sideloaded_tracks", "FEmusic_home")
)

View File

@ -0,0 +1,288 @@
package app.revanced.patches.music.layout.branding.icon
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.booleanOption
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.CUSTOM_BRANDING_ICON_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.playservice.is_7_23_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.ResourceUtils.setIconType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyResources
import app.revanced.util.getResourceGroup
import app.revanced.util.underBarOrThrow
import org.w3c.dom.Element
import java.io.File
import java.nio.file.Files
import java.nio.file.StandardCopyOption
private const val ADAPTIVE_ICON_BACKGROUND_FILE_NAME =
"adaptiveproduct_youtube_music_background_color_108"
private const val ADAPTIVE_ICON_FOREGROUND_FILE_NAME =
"adaptiveproduct_youtube_music_foreground_color_108"
private const val DEFAULT_ICON = "revancify_blue"
private val availableIcon = mapOf(
"AFN Blue" to "afn_blue",
"AFN Red" to "afn_red",
"MMT" to "mmt",
"Revancify Blue" to DEFAULT_ICON,
"Revancify Red" to "revancify_red",
"YouTube Music" to "youtube_music"
)
private val sizeArray = arrayOf(
"xxxhdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi"
)
private val largeSizeArray = arrayOf(
"xlarge-hdpi",
"xlarge-mdpi",
"large-xhdpi",
"large-hdpi",
"large-mdpi",
"xxhdpi",
"xhdpi",
"hdpi",
"mdpi",
)
private val largeDrawableDirectories = largeSizeArray.map { "drawable-$it" }
private val mipmapDirectories = sizeArray.map { "mipmap-$it" }
private val launcherIconResourceFileNames = arrayOf(
ADAPTIVE_ICON_BACKGROUND_FILE_NAME,
ADAPTIVE_ICON_FOREGROUND_FILE_NAME,
"ic_launcher_release"
).map { "$it.png" }.toTypedArray()
private val splashIconResourceFileNames = arrayOf(
// This file only exists in [drawable-hdpi]
// Since {@code ResourceUtils#copyResources} checks for null values before copying,
// Just adds it to the array.
"action_bar_logo_release",
"record"
).map { "$it.png" }.toTypedArray()
private val launcherIconResourceGroups =
mipmapDirectories.getResourceGroup(launcherIconResourceFileNames)
private val splashIconResourceGroups =
largeDrawableDirectories.getResourceGroup(splashIconResourceFileNames)
@Suppress("unused")
val customBrandingIconPatch = resourcePatch(
CUSTOM_BRANDING_ICON_FOR_YOUTUBE_MUSIC.title,
CUSTOM_BRANDING_ICON_FOR_YOUTUBE_MUSIC.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
versionCheckPatch,
)
val appIconOption = stringOption(
key = "appIcon",
default = DEFAULT_ICON,
values = availableIcon,
title = "App icon",
description = """
The icon to apply to the app.
If a path to a folder is provided, the folder must contain the following folders:
${mipmapDirectories.joinToString("\n") { "- $it" }}
Each of these folders must contain the following files:
${launcherIconResourceFileNames.joinToString("\n") { "- $it" }}
""".trimIndentMultiline(),
required = true,
)
val changeSplashIconOption by booleanOption(
key = "changeSplashIcon",
default = true,
title = "Change splash icons",
description = "Apply the custom branding icon to the splash screen.",
required = true
)
val restoreOldSplashIconOption by booleanOption(
key = "restoreOldSplashIcon",
default = false,
title = "Restore old splash icon",
description = """
Restore the old style splash icon.
If you enable both the old style splash icon and the Cairo splash animation,
Old style splash icon will appear first and then the Cairo splash animation will start.
""".trimIndentMultiline(),
required = true,
)
execute {
// Check patch options first.
val appIcon = appIconOption.underBarOrThrow()
val appIconResourcePath = "music/branding/$appIcon"
val youtubeMusicIconResourcePath = "music/branding/youtube_music"
val resourceDirectory = get("res")
// Check if a custom path is used in the patch options.
if (!availableIcon.containsValue(appIcon)) {
launcherIconResourceGroups.let { resourceGroups ->
try {
val path = File(appIcon)
resourceGroups.forEach { group ->
val fromDirectory = path.resolve(group.resourceDirectoryName)
val toDirectory = resourceDirectory.resolve(group.resourceDirectoryName)
group.resources.forEach { iconFileName ->
Files.write(
toDirectory.resolve(iconFileName).toPath(),
fromDirectory.resolve(iconFileName).readBytes()
)
}
}
} catch (_: Exception) {
// Exception is thrown if an invalid path is used in the patch option.
throw PatchException("Invalid app icon path: $appIcon")
}
}
} else {
// Change launcher icon.
launcherIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
copyResources("$appIconResourcePath/launcher", it)
}
}
// Change monochrome icon.
arrayOf(
ResourceGroup(
"drawable",
"ic_app_icons_themed_youtube_music.xml"
)
).forEach { resourceGroup ->
copyResources("$appIconResourcePath/monochrome", resourceGroup)
}
// Change splash icon.
if (restoreOldSplashIconOption == true) {
var oldSplashIconNotExists: Boolean
document("res/drawable/splash_screen.xml").use { document ->
document.apply {
val node = getElementsByTagName("layer-list").item(0)
oldSplashIconNotExists = (node as Element)
.getElementsByTagName("item")
.length == 1
if (oldSplashIconNotExists) {
createElement("item").also { itemNode ->
itemNode.appendChild(
createElement("bitmap").also { bitmapNode ->
bitmapNode.setAttribute("android:gravity", "center")
bitmapNode.setAttribute("android:src", "@drawable/record")
}
)
node.appendChild(itemNode)
}
}
}
}
if (oldSplashIconNotExists) {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
copyResources(
"$youtubeMusicIconResourcePath/splash",
it,
createDirectoryIfNotExist = true
)
}
}
}
}
// Change splash icon.
if (changeSplashIconOption == true) {
// Some resources have been removed in the latest YouTube Music.
// For compatibility, use try...catch.
try {
splashIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
copyResources("$appIconResourcePath/splash", it)
}
}
} catch (_: Exception) {
}
}
setIconType(appIcon)
}
updatePatchStatus(CUSTOM_BRANDING_ICON_FOR_YOUTUBE_MUSIC)
// region fix app icon
if (!is_7_23_or_greater) {
return@execute
}
if (appIcon == "youtube_music") {
return@execute
}
fun getAdaptiveIconResourceFile(tag: String): String {
document("res/mipmap-anydpi/ic_launcher_release.xml").use { document ->
val adaptiveIcon = document
.getElementsByTagName("adaptive-icon")
.item(0) as Element
val childNodes = adaptiveIcon.childNodes
for (i in 0 until childNodes.length) {
val node = childNodes.item(i)
if (node is Element && node.tagName == tag && node.hasAttribute("android:drawable")) {
return node.getAttribute("android:drawable").split("/")[1]
}
}
throw PatchException("Element not found: $tag")
}
}
mapOf(
ADAPTIVE_ICON_BACKGROUND_FILE_NAME to getAdaptiveIconResourceFile("background"),
ADAPTIVE_ICON_FOREGROUND_FILE_NAME to getAdaptiveIconResourceFile("foreground")
).forEach { (oldIconResourceFile, newIconResourceFile) ->
mipmapDirectories.forEach {
val mipmapDirectory = resourceDirectory.resolve(it)
Files.move(
mipmapDirectory
.resolve("$oldIconResourceFile.png")
.toPath(),
mipmapDirectory
.resolve("$newIconResourceFile.png")
.toPath(),
StandardCopyOption.REPLACE_EXISTING
)
}
}
// endregion
}
}

View File

@ -0,0 +1,81 @@
package app.revanced.patches.music.layout.branding.name
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.removeStringsElements
import app.revanced.util.valueOrThrow
private const val APP_NAME_NOTIFICATION = "ReVanced Extended Music"
private const val APP_NAME_LAUNCHER = "RVX Music"
@Suppress("unused")
val customBrandingNamePatch = resourcePatch(
CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC.title,
CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
val appNameNotificationOption = stringOption(
key = "appNameNotification",
default = APP_NAME_LAUNCHER,
values = mapOf(
"ReVanced Extended Music" to APP_NAME_NOTIFICATION,
"RVX Music" to APP_NAME_LAUNCHER,
"YouTube Music" to "YouTube Music",
"YT Music" to "YT Music",
),
title = "App name in notification panel",
description = "The name of the app as it appears in the notification panel.",
required = true
)
val appNameLauncherOption = stringOption(
key = "appNameLauncher",
default = APP_NAME_LAUNCHER,
values = mapOf(
"ReVanced Extended Music" to APP_NAME_NOTIFICATION,
"RVX Music" to APP_NAME_LAUNCHER,
"YouTube Music" to "YouTube Music",
"YT Music" to "YT Music",
),
title = "App name in launcher",
description = "The name of the app as it appears in the launcher.",
required = true
)
execute {
// Check patch options first.
val notificationName = appNameNotificationOption
.valueOrThrow()
val launcherName = appNameLauncherOption
.valueOrThrow()
removeStringsElements(
arrayOf("app_launcher_name", "app_name")
)
document("res/values/strings.xml").use { document ->
mapOf(
"app_name" to notificationName,
"app_launcher_name" to launcherName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
updatePatchStatus(CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC)
}
}

View File

@ -0,0 +1,176 @@
package app.revanced.patches.music.layout.header
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.CUSTOM_HEADER_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.settings.ResourceUtils.getIconType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.Utils.trimIndentMultiline
import app.revanced.util.copyFile
import app.revanced.util.copyResources
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.resolvable
import app.revanced.util.valueOrThrow
private const val DEFAULT_HEADER_KEY = "Custom branding icon"
private const val DEFAULT_HEADER_VALUE = "custom_branding_icon"
private val actionBarLogoResourceDirectoryNames = mapOf(
"xxxhdpi" to "320px x 96px",
"xxhdpi" to "240px x 72px",
"xhdpi" to "160px x 48px",
"hdpi" to "121px x 36px",
"mdpi" to "80px x 24px",
).map { (dpi, dim) ->
"drawable-$dpi" to dim
}.toMap()
private val logoMusicResourceDirectoryNames = mapOf(
"xxxhdpi" to "576px x 200px",
"xxhdpi" to "432px x 150px",
"xhdpi" to "288px x 100px",
"hdpi" to "217px x 76px",
"mdpi" to "144px x 50px",
).map { (dpi, dim) ->
"drawable-$dpi" to dim
}.toMap()
private val ytmMusicLogoResourceDirectoryNames = mapOf(
"xxxhdpi" to "412px x 144px",
"xxhdpi" to "309px x 108px",
"xhdpi" to "206px x 72px",
"hdpi" to "155px x 54px",
"mdpi" to "103px x 36px",
).map { (dpi, dim) ->
"drawable-$dpi" to dim
}.toMap()
private val headerIconResourceFileNames = arrayOf(
"action_bar_logo",
"logo_music",
"ytm_logo"
).map { "$it.png" }.toTypedArray()
private val headerIconResourceGroups =
actionBarLogoResourceDirectoryNames.keys.map { directory ->
ResourceGroup(
directory, *headerIconResourceFileNames
)
}
private val getDescription = {
var descriptionBody = """
The header to apply to the app.
Patch option '$DEFAULT_HEADER_KEY' applies only when:
1. Patch 'Custom branding icon for YouTube Music' is included.
2. Patch option for 'Custom branding icon for YouTube Music' is selected from the preset.
If a path to a folder is provided, the folder must contain one or more of the following folders, depending on the DPI of the device:
${actionBarLogoResourceDirectoryNames.keys.joinToString("\n") { "- $it" }}
Each of the folders must contain all of the following files:
${headerIconResourceFileNames.joinToString("\n") { "- $it" }}
"""
mapOf(
"action_bar_logo.png" to actionBarLogoResourceDirectoryNames,
"logo_music.png" to logoMusicResourceDirectoryNames,
"ytm_logo.png" to ytmMusicLogoResourceDirectoryNames
).forEach { (images, directoryNames) ->
descriptionBody += """
The image '$images' dimensions must be as follows:
${directoryNames.map { (dpi, dim) -> "- $dpi: $dim" }.joinToString("\n")}
"""
}
descriptionBody.trimIndentMultiline()
}
private val changeHeaderBytecodePatch = bytecodePatch(
description = "changeHeaderBytecodePatch"
) {
execute {
/**
* New Header has been added from YouTube Music v7.04.51.
*
* The new header's file names are 'action_bar_logo_ringo2.png' and 'ytm_logo_ringo2.png'.
* The only difference between the existing header and the new header is the dimensions of the image.
*
* The affected patch is [changeHeaderPatch].
*
* TODO: Add a new header image file to [changeHeaderPatch] later.
*/
if (!headerSwitchConfigFingerprint.resolvable()) {
return@execute
}
headerSwitchConfigFingerprint.injectLiteralInstructionBooleanCall(
45617851L,
"0x0"
)
}
}
@Suppress("unused")
val changeHeaderPatch = resourcePatch(
CUSTOM_HEADER_FOR_YOUTUBE_MUSIC.title,
CUSTOM_HEADER_FOR_YOUTUBE_MUSIC.summary,
use = false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
changeHeaderBytecodePatch,
settingsPatch,
)
val customHeaderOption = stringOption(
key = "customHeader",
default = DEFAULT_HEADER_VALUE,
values = mapOf(
DEFAULT_HEADER_KEY to DEFAULT_HEADER_VALUE
),
title = "Custom header",
description = getDescription(),
required = true,
)
execute {
// Check patch options first.
val customHeader = customHeaderOption
.valueOrThrow()
val customBrandingIconType = getIconType()
val customBrandingIconIncluded = customBrandingIconType != "default"
val warnings = "WARNING: Invalid header path: $customHeader. Does not apply patches."
if (customHeader != DEFAULT_HEADER_VALUE) {
copyFile(
headerIconResourceGroups,
customHeader,
warnings
)
} else if (customBrandingIconIncluded) {
headerIconResourceGroups.let { resourceGroups ->
resourceGroups.forEach {
copyResources("music/branding/$customBrandingIconType/header", it)
}
}
} else {
println(warnings)
}
updatePatchStatus(CUSTOM_HEADER_FOR_YOUTUBE_MUSIC)
}
}

View File

@ -0,0 +1,12 @@
package app.revanced.patches.music.layout.header
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val headerSwitchConfigFingerprint = legacyFingerprint(
name = "headerSwitchConfigFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(45617851L)
)

View File

@ -0,0 +1,17 @@
package app.revanced.patches.music.layout.overlayfilter
import app.revanced.patches.music.utils.resourceid.designBottomSheetDialog
import app.revanced.util.fingerprint.legacyFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val designBottomSheetDialogFingerprint = legacyFingerprint(
name = "designBottomSheetDialogFingerprint",
returnType = "V",
parameters = emptyList(),
opcodes = listOf(
Opcode.IF_NEZ,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT
),
literals = listOf(designBottomSheetDialog)
)

View File

@ -0,0 +1,67 @@
package app.revanced.patches.music.layout.overlayfilter
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.GENERAL_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.HIDE_OVERLAY_FILTER
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private val overlayFilterBytecodePatch = bytecodePatch(
description = "overlayFilterBytecodePatch"
) {
dependsOn(sharedResourceIdPatch)
execute {
designBottomSheetDialogFingerprint.matchOrThrow().let {
it.method.apply {
val insertIndex = it.patternMatch!!.endIndex - 1
val freeRegister = getInstruction<OneRegisterInstruction>(insertIndex + 1).registerA
addInstructions(
insertIndex, """
invoke-virtual {p0}, $definingClass->getWindow()Landroid/view/Window;
move-result-object v$freeRegister
invoke-static {v$freeRegister}, $GENERAL_CLASS_DESCRIPTOR->disableDimBehind(Landroid/view/Window;)V
"""
)
}
}
}
}
@Suppress("unused")
val overlayFilterPatch = resourcePatch(
HIDE_OVERLAY_FILTER.title,
HIDE_OVERLAY_FILTER.summary,
use = false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
overlayFilterBytecodePatch,
)
execute {
val styleFile = get("res/values/styles.xml")
styleFile.writeText(
styleFile.readText()
.replace(
"ytOverlayBackgroundMedium\">@color/yt_black_pure_opacity60",
"ytOverlayBackgroundMedium\">@android:color/transparent"
)
)
updatePatchStatus(HIDE_OVERLAY_FILTER)
}
}

View File

@ -0,0 +1,27 @@
package app.revanced.patches.music.layout.playeroverlay
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.HIDE_PLAYER_OVERLAY_FILTER
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.util.removeOverlayBackground
@Suppress("unused")
val playerOverlayFilterPatch = resourcePatch(
HIDE_PLAYER_OVERLAY_FILTER.title,
HIDE_PLAYER_OVERLAY_FILTER.summary,
use = false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
execute {
removeOverlayBackground(
arrayOf("music_controls_overlay.xml"),
arrayOf("player_control_screen")
)
updatePatchStatus(HIDE_PLAYER_OVERLAY_FILTER)
}
}

View File

@ -0,0 +1,71 @@
package app.revanced.patches.music.layout.translations
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.TRANSLATIONS_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.translations.APP_LANGUAGES
import app.revanced.patches.shared.translations.baseTranslationsPatch
// Array of supported translations, each represented by its language code.
private val SUPPORTED_TRANSLATIONS = setOf(
"bg-rBG", "bn", "cs-rCZ", "el-rGR", "es-rES", "fr-rFR", "hu-rHU", "id-rID", "in", "it-rIT",
"ja-rJP", "ko-rKR", "nl-rNL", "pl-rPL", "pt-rBR", "ro-rRO", "ru-rRU", "tr-rTR", "uk-rUA",
"vi-rVN", "zh-rCN", "zh-rTW"
)
@Suppress("unused")
val translationsPatch = resourcePatch(
TRANSLATIONS_FOR_YOUTUBE_MUSIC.title,
TRANSLATIONS_FOR_YOUTUBE_MUSIC.summary,
) {
val customTranslations by stringOption(
key = "customTranslations",
default = "",
title = "Custom translations",
description = """
The path to the 'strings.xml' file.
Please note that applying the 'strings.xml' file will overwrite all existing translations.
""".trimIndent(),
required = true,
)
val selectedTranslations by stringOption(
key = "selectedTranslations",
default = SUPPORTED_TRANSLATIONS.joinToString(", "),
title = "Translations to add",
description = "A list of translations to be added for the RVX settings, separated by commas.",
required = true,
)
val selectedStringResources by stringOption(
key = "selectedStringResources",
default = APP_LANGUAGES.joinToString(", "),
title = "String resources to keep",
description = """
A list of string resources to be kept, separated by commas.
String resources not in the list will be removed from the app.
Default string resource, English, is not removed.
""".trimIndent(),
required = true,
)
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
)
execute {
baseTranslationsPatch(
customTranslations, selectedTranslations, selectedStringResources,
SUPPORTED_TRANSLATIONS, "music"
)
updatePatchStatus(TRANSLATIONS_FOR_YOUTUBE_MUSIC)
}
}

View File

@ -0,0 +1,154 @@
package app.revanced.patches.music.layout.visual
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.music.layout.branding.icon.customBrandingIconPatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.settings.ResourceUtils.SETTINGS_HEADER_PATH
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.ResourceGroup
import app.revanced.util.copyResources
import app.revanced.util.doRecursively
import app.revanced.util.getStringOptionValue
import app.revanced.util.underBarOrThrow
import org.w3c.dom.Element
private const val DEFAULT_ICON = "extension"
@Suppress("unused")
val visualPreferencesIconsPatch = resourcePatch(
VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC.title,
VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
val settingsMenuIconOption = stringOption(
key = "settingsMenuIcon",
default = DEFAULT_ICON,
values = mapOf(
"Custom branding icon" to "custom_branding_icon",
"Extension" to DEFAULT_ICON,
"Gear" to "gear",
"ReVanced" to "revanced",
"ReVanced Colored" to "revanced_colored",
),
title = "RVX settings menu icon",
description = "The icon for the RVX settings menu.",
required = true,
)
execute {
// Check patch options first.
val selectedIconType = settingsMenuIconOption
.underBarOrThrow()
val appIconOption = customBrandingIconPatch
.getStringOptionValue("appIcon")
val customBrandingIconType = appIconOption
.underBarOrThrow()
// region copy shared resources.
arrayOf(
ResourceGroup(
"drawable",
*preferenceKey.map { it + "_icon.xml" }.toTypedArray()
),
).forEach { resourceGroup ->
copyResources("music/visual/shared", resourceGroup)
}
// endregion.
// region copy RVX settings menu icon.
val iconPath = when (selectedIconType) {
"custom_branding_icon" -> "music/branding/$customBrandingIconType/settings"
else -> "music/visual/icons/$selectedIconType"
}
val resourceGroup = ResourceGroup(
"drawable",
"revanced_extended_settings_icon.xml"
)
try {
copyResources(iconPath, resourceGroup)
} catch (_: Exception) {
// Ignore if resource copy fails
}
// endregion.
updatePatchStatus(VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC)
}
finalize {
// region set visual preferences icon.
document(SETTINGS_HEADER_PATH).use { document ->
document.doRecursively loop@{ node ->
if (node !is Element) return@loop
node.getAttributeNode("android:key")
?.textContent
?.removePrefix("@string/")
?.let { title ->
val drawableName = when (title) {
in preferenceKey -> title + "_icon"
else -> null
}
drawableName?.let {
node.setAttribute("android:icon", "@drawable/$it")
}
}
}
}
// endregion.
}
}
// region preference key and icon.
private val preferenceKey = setOf(
// YouTube settings.
"pref_key_parent_tools",
"settings_header_general",
"settings_header_playback",
"settings_header_data_saving",
"settings_header_downloads_and_storage",
"settings_header_notifications",
"settings_header_privacy_and_location",
"settings_header_recommendations",
"settings_header_paid_memberships",
"settings_header_about_youtube_music",
// RVX settings.
"revanced_extended_settings",
"revanced_preference_screen_account",
"revanced_preference_screen_action_bar",
"revanced_preference_screen_ads",
"revanced_preference_screen_flyout",
"revanced_preference_screen_general",
"revanced_preference_screen_navigation",
"revanced_preference_screen_player",
"revanced_preference_screen_settings",
"revanced_preference_screen_video",
"revanced_preference_screen_ryd",
"revanced_preference_screen_return_youtube_username",
"revanced_preference_screen_sb",
"revanced_preference_screen_misc",
)
// endregion.

View File

@ -0,0 +1,107 @@
package app.revanced.patches.music.misc.backgroundplayback
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.resolvable
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstStringInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
@Suppress("unused")
val backgroundPlaybackPatch = bytecodePatch(
REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS.title,
REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
// region patch for background play
backgroundPlaybackManagerFingerprint.methodOrThrow().addInstructions(
0, """
const/4 v0, 0x1
return v0
"""
)
// endregion
// region patch for exclusive audio playback
// don't play music video
musicBrowserServiceFingerprint.matchOrThrow().let {
it.method.apply {
val stringIndex = it.stringMatches!!.first().index
val targetIndex = indexOfFirstInstructionOrThrow(stringIndex) {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "Z" &&
reference.parameterTypes.size == 0
}
getWalkerMethod(targetIndex).addInstructions(
0, """
const/4 v0, 0x1
return v0
"""
)
}
}
// don't play podcast videos
// enable by default from YouTube Music 7.05.52+
if (podCastConfigFingerprint.resolvable() &&
dataSavingSettingsFragmentFingerprint.resolvable()
) {
podCastConfigFingerprint.methodOrThrow().apply {
val insertIndex = implementation!!.instructions.size - 1
val targetRegister = getInstruction<OneRegisterInstruction>(insertIndex).registerA
addInstruction(
insertIndex,
"const/4 v$targetRegister, 0x1"
)
}
dataSavingSettingsFragmentFingerprint.methodOrThrow().apply {
val insertIndex =
indexOfFirstStringInstructionOrThrow("pref_key_dont_play_nma_video") + 4
val targetRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstruction(
insertIndex,
"const/4 v$targetRegister, 0x1"
)
}
}
// endregion
// region patch for minimized playback
kidsBackgroundPlaybackPolicyControllerFingerprint.methodOrThrow().addInstruction(
0, "return-void"
)
// endregion
updatePatchStatus(REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS)
}
}

View File

@ -0,0 +1,65 @@
package app.revanced.patches.music.misc.backgroundplayback
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val backgroundPlaybackManagerFingerprint = legacyFingerprint(
name = "backgroundPlaybackManagerFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L"),
literals = listOf(64657230L),
)
internal val dataSavingSettingsFragmentFingerprint = legacyFingerprint(
name = "dataSavingSettingsFragmentFingerprint",
returnType = "V",
parameters = listOf("Landroid/os/Bundle;", "Ljava/lang/String;"),
strings = listOf("pref_key_dont_play_nma_video"),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/DataSavingSettingsFragment;") &&
method.name == "onCreatePreferences"
}
)
internal val kidsBackgroundPlaybackPolicyControllerFingerprint = legacyFingerprint(
name = "kidsBackgroundPlaybackPolicyControllerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("I", "L", "Z"),
opcodes = listOf(
Opcode.IGET,
Opcode.IF_NE,
Opcode.IGET_OBJECT,
Opcode.IF_NE,
Opcode.IGET_BOOLEAN,
Opcode.IF_EQ,
Opcode.GOTO,
Opcode.RETURN_VOID,
Opcode.SGET_OBJECT,
Opcode.CONST_4,
Opcode.IF_NE,
Opcode.IPUT_BOOLEAN
)
)
internal val musicBrowserServiceFingerprint = legacyFingerprint(
name = "musicBrowserServiceFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Ljava/lang/String;", "Landroid/os/Bundle;"),
strings = listOf("android.service.media.extra.RECENT"),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicBrowserService;")
},
)
internal val podCastConfigFingerprint = legacyFingerprint(
name = "podCastConfigFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(45388403L),
)

View File

@ -0,0 +1,41 @@
package app.revanced.patches.music.misc.bitrate
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.BITRATE_DEFAULT_VALUE
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
@Suppress("unused")
val bitrateDefaultValuePatch = resourcePatch(
BITRATE_DEFAULT_VALUE.title,
BITRATE_DEFAULT_VALUE.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
document("res/xml/data_saving_settings.xml").use { document ->
document.getElementsByTagName("com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat")
.item(0).childNodes.apply {
arrayOf("BitrateAudioMobile", "BitrateAudioWiFi").forEach {
for (i in 1 until length) {
val view = item(i)
if (
view.hasAttributes() &&
view.attributes.getNamedItem("android:key").nodeValue.endsWith(it)
) {
view.attributes.getNamedItem("android:defaultValue").nodeValue =
"Always High"
break
}
}
}
}
}
updatePatchStatus(BITRATE_DEFAULT_VALUE)
}
}

View File

@ -0,0 +1,37 @@
package app.revanced.patches.music.misc.codecs
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.patch.PatchList.ENABLE_OPUS_CODEC
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.opus.baseOpusCodecsPatch
@Suppress("unused")
val opusCodecPatch = resourcePatch(
ENABLE_OPUS_CODEC.title,
ENABLE_OPUS_CODEC.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseOpusCodecsPatch(
"$MISC_PATH/OpusCodecPatch;->enableOpusCodec()Z"
),
settingsPatch
)
execute {
addSwitchPreference(
CategoryType.MISC,
"revanced_enable_opus_codec",
"false"
)
updatePatchStatus(ENABLE_OPUS_CODEC)
}
}

View File

@ -0,0 +1,36 @@
package app.revanced.patches.music.misc.debugging
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.ENABLE_DEBUG_LOGGING
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
@Suppress("unused")
val debuggingPatch = resourcePatch(
ENABLE_DEBUG_LOGGING.title,
ENABLE_DEBUG_LOGGING.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
addSwitchPreference(
CategoryType.MISC,
"revanced_enable_debug_logging",
"false"
)
addSwitchPreference(
CategoryType.MISC,
"revanced_enable_debug_buffer_logging",
"false",
"revanced_enable_debug_logging"
)
updatePatchStatus(ENABLE_DEBUG_LOGGING)
}
}

View File

@ -0,0 +1,14 @@
package app.revanced.patches.music.misc.share
import app.revanced.patches.music.utils.resourceid.bottomSheetRecyclerView
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val bottomSheetRecyclerViewFingerprint = legacyFingerprint(
name = "bottomSheetRecyclerViewFingerprint",
returnType = "Lj${'$'}/util/Optional;",
accessFlags = AccessFlags.PROTECTED or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(bottomSheetRecyclerView),
)

View File

@ -0,0 +1,66 @@
package app.revanced.patches.music.misc.share
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.COMPONENTS_PATH
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.patch.PatchList.CHANGE_SHARE_SHEET
import app.revanced.patches.music.utils.resourceid.bottomSheetRecyclerView
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.litho.addLithoFilter
import app.revanced.patches.shared.litho.lithoFilterPatch
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"$MISC_PATH/ShareSheetPatch;"
private const val FILTER_CLASS_DESCRIPTOR =
"$COMPONENTS_PATH/ShareSheetMenuFilter;"
@Suppress("unused")
val shareSheetPatch = bytecodePatch(
CHANGE_SHARE_SHEET.title,
CHANGE_SHARE_SHEET.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
lithoFilterPatch,
settingsPatch,
sharedResourceIdPatch
)
execute {
bottomSheetRecyclerViewFingerprint.methodOrThrow().apply {
val constIndex = indexOfFirstLiteralInstructionOrThrow(bottomSheetRecyclerView)
val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST)
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $EXTENSION_CLASS_DESCRIPTOR->onShareSheetMenuCreate(Landroid/support/v7/widget/RecyclerView;)V"
)
}
addLithoFilter(FILTER_CLASS_DESCRIPTOR)
addSwitchPreference(
CategoryType.MISC,
"revanced_change_share_sheet",
"false"
)
updatePatchStatus(CHANGE_SHARE_SHEET)
}
}

View File

@ -0,0 +1,101 @@
package app.revanced.patches.music.misc.splash
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.utils.extension.Constants.MISC_PATH
import app.revanced.patches.music.utils.patch.PatchList.DISABLE_CAIRO_SPLASH_ANIMATION
import app.revanced.patches.music.utils.playservice.is_7_06_or_greater
import app.revanced.patches.music.utils.playservice.is_7_20_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.resourceid.mainActivityLaunchAnimation
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val EXTENSION_METHOD_DESCRIPTOR =
"$MISC_PATH/CairoSplashAnimationPatch;->disableCairoSplashAnimation(Z)Z"
@Suppress("unused")
val cairoSplashAnimationPatch = bytecodePatch(
DISABLE_CAIRO_SPLASH_ANIMATION.title,
DISABLE_CAIRO_SPLASH_ANIMATION.summary,
) {
compatibleWith(
YOUTUBE_MUSIC_PACKAGE_NAME(
"7.06.54",
"7.16.53",
),
)
dependsOn(
settingsPatch,
sharedResourceIdPatch,
versionCheckPatch,
)
execute {
if (!is_7_06_or_greater) {
println("WARNING: This patch is not supported in this version. Use YouTube Music 7.06.54 or later.")
return@execute
} else if (!is_7_20_or_greater) {
cairoSplashAnimationConfigFingerprint.injectLiteralInstructionBooleanCall(
45635386L,
EXTENSION_METHOD_DESCRIPTOR
)
} else {
cairoSplashAnimationConfigFingerprint.methodOrThrow().apply {
val literalIndex = indexOfFirstLiteralInstructionOrThrow(
mainActivityLaunchAnimation
)
val insertIndex = indexOfFirstInstructionReversedOrThrow(literalIndex) {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "setContentView"
} + 1
val viewStubFindViewByIdIndex = indexOfFirstInstructionOrThrow(literalIndex) {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.name == "findViewById" &&
reference.definingClass != "Landroid/view/View;"
}
val freeRegister =
getInstruction<FiveRegisterInstruction>(viewStubFindViewByIdIndex).registerD
val jumpIndex = indexOfFirstInstructionReversedOrThrow(
viewStubFindViewByIdIndex,
Opcode.IGET_OBJECT
)
addInstructionsWithLabels(
insertIndex, """
const/4 v$freeRegister, 0x1
invoke-static {v$freeRegister}, $EXTENSION_METHOD_DESCRIPTOR
move-result v$freeRegister
if-eqz v$freeRegister, :skip
""", ExternalLabel("skip", getInstruction(jumpIndex))
)
}
}
addSwitchPreference(
CategoryType.MISC,
"revanced_disable_cairo_splash_animation",
"false"
)
updatePatchStatus(DISABLE_CAIRO_SPLASH_ANIMATION)
}
}

View File

@ -0,0 +1,26 @@
package app.revanced.patches.music.misc.splash
import app.revanced.patches.music.utils.playservice.is_7_20_or_greater
import app.revanced.patches.music.utils.resourceid.mainActivityLaunchAnimation
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.indexOfFirstLiteralInstruction
/**
* This fingerprint is compatible with YouTube Music v7.06.53+
*/
internal val cairoSplashAnimationConfigFingerprint = legacyFingerprint(
name = "cairoSplashAnimationConfigFingerprint",
returnType = "V",
customFingerprint = handler@{ method, _ ->
if (method.definingClass != "Lcom/google/android/apps/youtube/music/activities/MusicActivity;")
return@handler false
if (method.name != "onCreate")
return@handler false
if (is_7_20_or_greater) {
method.indexOfFirstLiteralInstruction(mainActivityLaunchAnimation) >= 0
} else {
method.indexOfFirstLiteralInstruction(45635386) >= 0
}
}
)

View File

@ -0,0 +1,37 @@
package app.revanced.patches.music.misc.thumbnails
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.BYPASS_IMAGE_REGION_RESTRICTIONS
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.imageurl.addImageUrlHook
import app.revanced.patches.shared.imageurl.cronetImageUrlHookPatch
@Suppress("unused")
val bypassImageRegionRestrictionsPatch = bytecodePatch(
BYPASS_IMAGE_REGION_RESTRICTIONS.title,
BYPASS_IMAGE_REGION_RESTRICTIONS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsPatch,
cronetImageUrlHookPatch(false)
)
execute {
addImageUrlHook()
addSwitchPreference(
CategoryType.MISC,
"revanced_bypass_image_region_restrictions",
"false"
)
updatePatchStatus(BYPASS_IMAGE_REGION_RESTRICTIONS)
}
}

View File

@ -0,0 +1,34 @@
package app.revanced.patches.music.misc.tracking
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.SANITIZE_SHARING_LINKS
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.tracking.baseSanitizeUrlQueryPatch
@Suppress("unused")
val sanitizeUrlQueryPatch = bytecodePatch(
SANITIZE_SHARING_LINKS.title,
SANITIZE_SHARING_LINKS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseSanitizeUrlQueryPatch,
settingsPatch,
)
execute {
addSwitchPreference(
CategoryType.MISC,
"revanced_sanitize_sharing_links",
"true"
)
updatePatchStatus(SANITIZE_SHARING_LINKS)
}
}

View File

@ -0,0 +1,34 @@
package app.revanced.patches.music.navigation.components
import app.revanced.patches.music.utils.resourceid.colorGrey
import app.revanced.patches.music.utils.resourceid.text1
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val tabLayoutFingerprint = legacyFingerprint(
name = "tabLayoutFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
strings = listOf("FEmusic_radio_builder"),
literals = listOf(colorGrey)
)
internal val tabLayoutTextFingerprint = legacyFingerprint(
name = "tabLayoutTextFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.IGET,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.INVOKE_INTERFACE,
Opcode.MOVE_RESULT
),
literals = listOf(text1)
)

View File

@ -0,0 +1,174 @@
package app.revanced.patches.music.navigation.components
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.NAVIGATION_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.patch.PatchList.NAVIGATION_BAR_COMPONENTS
import app.revanced.patches.music.utils.resourceid.colorGrey
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.resourceid.text1
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstLiteralInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val FLAG = "android:layout_weight"
private const val RESOURCE_FILE_PATH = "res/layout/image_with_text_tab.xml"
private val navigationBarComponentsResourcePatch = resourcePatch(
description = "navigationBarComponentsResourcePatch"
) {
execute {
document(RESOURCE_FILE_PATH).use { document ->
with(document.getElementsByTagName("ImageView").item(0)) {
if (attributes.getNamedItem(FLAG) != null)
return@with
document.createAttribute(FLAG)
.apply { value = "0.5" }
.let(attributes::setNamedItem)
}
}
}
}
@Suppress("unused")
val navigationBarComponentsPatch = bytecodePatch(
NAVIGATION_BAR_COMPONENTS.title,
NAVIGATION_BAR_COMPONENTS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
navigationBarComponentsResourcePatch,
sharedResourceIdPatch,
settingsPatch,
)
execute {
/**
* Enable black navigation bar
*/
tabLayoutFingerprint.methodOrThrow().apply {
val constIndex = indexOfFirstLiteralInstructionOrThrow(colorGrey)
val insertIndex = indexOfFirstInstructionOrThrow(constIndex) {
opcode == Opcode.INVOKE_VIRTUAL
&& getReference<MethodReference>()?.name == "setBackgroundColor"
}
val insertRegister = getInstruction<FiveRegisterInstruction>(insertIndex).registerD
addInstructions(
insertIndex, """
invoke-static {}, $NAVIGATION_CLASS_DESCRIPTOR->enableBlackNavigationBar()I
move-result v$insertRegister
"""
)
}
/**
* Hide navigation labels
*/
tabLayoutTextFingerprint.methodOrThrow().apply {
val constIndex =
indexOfFirstLiteralInstructionOrThrow(text1)
val targetIndex = indexOfFirstInstructionOrThrow(constIndex, Opcode.CHECK_CAST)
val targetParameter = getInstruction<ReferenceInstruction>(targetIndex).reference
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
if (!targetParameter.toString().endsWith("Landroid/widget/TextView;"))
throw PatchException("Method signature parameter did not match: $targetParameter")
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $NAVIGATION_CLASS_DESCRIPTOR->hideNavigationLabel(Landroid/widget/TextView;)V"
)
}
/**
* Hide navigation bar & buttons
*/
tabLayoutTextFingerprint.matchOrThrow().let {
it.method.apply {
val enumIndex = it.patternMatch!!.startIndex + 3
val enumRegister = getInstruction<OneRegisterInstruction>(enumIndex).registerA
val insertEnumIndex = indexOfFirstInstructionOrThrow(Opcode.AND_INT_LIT8) - 2
val pivotTabIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "getVisibility"
}
val pivotTabRegister =
getInstruction<FiveRegisterInstruction>(pivotTabIndex).registerC
addInstruction(
pivotTabIndex,
"invoke-static {v$pivotTabRegister}, $NAVIGATION_CLASS_DESCRIPTOR->hideNavigationButton(Landroid/view/View;)V"
)
addInstruction(
insertEnumIndex,
"sput-object v$enumRegister, $NAVIGATION_CLASS_DESCRIPTOR->lastPivotTab:Ljava/lang/Enum;"
)
}
}
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_enable_black_navigation_bar",
"true"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_home_button",
"false"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_samples_button",
"false"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_explore_button",
"false"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_library_button",
"false"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_upgrade_button",
"true"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_bar",
"false"
)
addSwitchPreference(
CategoryType.NAVIGATION,
"revanced_hide_navigation_label",
"false"
)
updatePatchStatus(NAVIGATION_BAR_COMPONENTS)
}
}

View File

@ -0,0 +1,356 @@
package app.revanced.patches.music.player.components
import app.revanced.patches.music.utils.extension.Constants.PLAYER_CLASS_DESCRIPTOR
import app.revanced.patches.music.utils.playservice.is_7_18_or_greater
import app.revanced.patches.music.utils.resourceid.colorGrey
import app.revanced.patches.music.utils.resourceid.darkBackground
import app.revanced.patches.music.utils.resourceid.miniPlayerDefaultText
import app.revanced.patches.music.utils.resourceid.miniPlayerMdxPlaying
import app.revanced.patches.music.utils.resourceid.miniPlayerPlayPauseReplayButton
import app.revanced.patches.music.utils.resourceid.miniPlayerViewPager
import app.revanced.patches.music.utils.resourceid.playerViewPager
import app.revanced.patches.music.utils.resourceid.remixGenericButtonSize
import app.revanced.patches.music.utils.resourceid.tapBloomView
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
const val AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY =
"Lcom/google/android/apps/youtube/music/player/AudioVideoSwitcherToggleView;->setVisibility(I)V"
internal val audioVideoSwitchToggleFingerprint = legacyFingerprint(
name = "audioVideoSwitchToggleFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
customFingerprint = { method, _ ->
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == AUDIO_VIDEO_SWITCH_TOGGLE_VISIBILITY
} >= 0
}
)
internal val engagementPanelHeightFingerprint = legacyFingerprint(
name = "engagementPanelHeightFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
// In YouTube Music 7.21.50+, there are two methods with similar structure, so this Opcode pattern must be used.
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
),
parameters = emptyList(),
customFingerprint = { method, _ ->
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "booleanValue"
} >= 0
}
)
internal val engagementPanelHeightParentFingerprint = legacyFingerprint(
name = "engagementPanelHeightParentFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(Opcode.NEW_ARRAY),
parameters = emptyList(),
customFingerprint = custom@{ method, _ ->
if (method.definingClass.startsWith("Lcom/")) {
return@custom false
}
if (method.returnType == "Ljava/lang/Object;") {
return@custom false
}
method.indexOfFirstInstruction {
opcode == Opcode.CHECK_CAST &&
(this as? ReferenceInstruction)?.reference?.toString() == "Lcom/google/android/libraries/youtube/engagementpanel/size/EngagementPanelSizeBehavior;"
} >= 0
}
)
internal val handleSearchRenderedFingerprint = legacyFingerprint(
name = "handleSearchRenderedFingerprint",
returnType = "V",
parameters = listOf("L"),
customFingerprint = { method, _ -> method.name == "handleSearchRendered" }
)
internal val handleSignInEventFingerprint = legacyFingerprint(
name = "handleSignInEventFingerprint",
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { method, _ -> method.name == "handleSignInEvent" }
)
internal val interactionLoggingEnumFingerprint = legacyFingerprint(
name = "interactionLoggingEnumFingerprint",
returnType = "V",
strings = listOf("INTERACTION_LOGGING_GESTURE_TYPE_SWIPE")
)
internal val minimizedPlayerFingerprint = legacyFingerprint(
name = "minimizedPlayerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ,
Opcode.IF_EQZ
),
strings = listOf("w_st")
)
internal val miniPlayerConstructorFingerprint = legacyFingerprint(
name = "miniPlayerConstructorFingerprint",
returnType = "V",
strings = listOf("sharedToggleMenuItemMutations"),
literals = listOf(colorGrey, miniPlayerPlayPauseReplayButton)
)
internal val miniPlayerDefaultTextFingerprint = legacyFingerprint(
name = "miniPlayerDefaultTextFingerprint",
returnType = "V",
parameters = listOf("Ljava/lang/Object;"),
opcodes = listOf(
Opcode.SGET_OBJECT,
Opcode.IF_NE
),
literals = listOf(miniPlayerDefaultText)
)
internal val miniPlayerDefaultViewVisibilityFingerprint = legacyFingerprint(
name = "miniPlayerDefaultViewVisibilityFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;", "F"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.SUB_FLOAT_2ADDR,
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL
),
customFingerprint = { method, classDef ->
method.name == "a" &&
classDef.methods.count() == 3
}
)
internal val miniPlayerParentFingerprint = legacyFingerprint(
name = "miniPlayerParentFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(miniPlayerMdxPlaying)
)
internal val mppWatchWhileLayoutFingerprint = legacyFingerprint(
name = "mppWatchWhileLayoutFingerprint",
returnType = "V",
opcodes = listOf(Opcode.NEW_ARRAY),
literals = listOf(miniPlayerPlayPauseReplayButton),
customFingerprint = custom@{ method, _ ->
if (!method.definingClass.endsWith("/MppWatchWhileLayout;")) {
return@custom false
}
if (method.name != "onFinishInflate") {
return@custom false
}
if (!is_7_18_or_greater) {
return@custom true
}
indexOfCallableInstruction(method) >= 0
}
)
internal fun indexOfCallableInstruction(method: Method) =
method.indexOfFirstInstruction {
val reference = getReference<MethodReference>()
opcode == Opcode.INVOKE_VIRTUAL &&
reference?.returnType == "V" &&
reference.parameterTypes.size == 1 &&
reference.parameterTypes.firstOrNull() == "Ljava/util/concurrent/Callable;"
}
internal val musicActivityWidgetFingerprint = legacyFingerprint(
name = "musicActivityWidgetFingerprint",
literals = listOf(79500L),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicActivity;")
}
)
internal val musicPlaybackControlsFingerprint = legacyFingerprint(
name = "musicPlaybackControlsFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Z"),
opcodes = listOf(
Opcode.IPUT_BOOLEAN,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicPlaybackControls;")
}
)
internal val nextButtonVisibilityFingerprint = legacyFingerprint(
name = "nextButtonVisibilityFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.CONST_16,
Opcode.IF_EQZ
)
)
internal val oldEngagementPanelFingerprint = legacyFingerprint(
name = "oldEngagementPanelFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45427672L),
)
/**
* Deprecated in YouTube Music v6.34.51+
*/
internal val oldPlayerBackgroundFingerprint = legacyFingerprint(
name = "oldPlayerBackgroundFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45415319L),
)
/**
* Deprecated in YouTube Music v6.31.55+
*/
internal val oldPlayerLayoutFingerprint = legacyFingerprint(
name = "oldPlayerLayoutFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45399578L),
)
internal val playerPatchConstructorFingerprint = legacyFingerprint(
name = "playerPatchConstructorFingerprint",
returnType = "V",
customFingerprint = { method, _ ->
method.definingClass == PLAYER_CLASS_DESCRIPTOR &&
method.name == "<init>"
}
)
internal val playerViewPagerConstructorFingerprint = legacyFingerprint(
name = "playerViewPagerConstructorFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
literals = listOf(miniPlayerViewPager, playerViewPager),
)
internal val quickSeekOverlayFingerprint = legacyFingerprint(
name = "quickSeekOverlayFingerprint",
returnType = "V",
parameters = emptyList(),
literals = listOf(darkBackground, tapBloomView),
)
internal val remixGenericButtonFingerprint = legacyFingerprint(
name = "remixGenericButtonFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.CONST,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.FLOAT_TO_INT
),
literals = listOf(remixGenericButtonSize),
)
internal val repeatTrackFingerprint = legacyFingerprint(
name = "repeatTrackFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "L"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.IGET_OBJECT,
Opcode.SGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT,
Opcode.IF_NEZ
),
strings = listOf("w_st")
)
internal val shuffleOnClickFingerprint = legacyFingerprint(
name = "shuffleOnClickFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("Landroid/view/View;"),
literals = listOf(45468L),
customFingerprint = { method, _ ->
method.name == "onClick" &&
indexOfAccessibilityInstruction(method) >= 0
}
)
internal fun indexOfAccessibilityInstruction(method: Method) =
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "announceForAccessibility"
}
internal val swipeToCloseFingerprint = legacyFingerprint(
name = "swipeToCloseFingerprint",
returnType = "Z",
parameters = emptyList(),
literals = listOf(45398432L),
)
internal val switchToggleColorFingerprint = legacyFingerprint(
name = "switchToggleColorFingerprint",
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = listOf("L", "J"),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.CHECK_CAST,
Opcode.IGET
)
)
internal val zenModeFingerprint = legacyFingerprint(
name = "zenModeFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L", "J"),
opcodes = listOf(
Opcode.MOVE_RESULT,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT,
Opcode.GOTO,
Opcode.NOP,
Opcode.SGET_OBJECT
)
)

View File

@ -0,0 +1,35 @@
package app.revanced.patches.music.utils
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val pendingIntentReceiverFingerprint = legacyFingerprint(
name = "pendingIntentReceiverFingerprint",
returnType = "V",
strings = listOf("YTM Dislike", "YTM Next", "YTM Previous"),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/PendingIntentReceiver;")
}
)
internal val playbackSpeedFingerprint = legacyFingerprint(
name = "playbackSpeedFingerprint",
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.CHECK_CAST,
Opcode.CONST_HIGH16,
Opcode.INVOKE_VIRTUAL
)
)
internal val playbackSpeedParentFingerprint = legacyFingerprint(
name = "playbackSpeedParentFingerprint",
returnType = "V",
accessFlags = AccessFlags.PRIVATE or AccessFlags.FINAL,
parameters = emptyList(),
strings = listOf("BT metadata: %s, %s, %s")
)

View File

@ -0,0 +1,19 @@
package app.revanced.patches.music.utils.compatibility
import app.revanced.patcher.patch.PackageName
import app.revanced.patcher.patch.VersionName
internal object Constants {
internal const val YOUTUBE_MUSIC_PACKAGE_NAME = "com.google.android.apps.youtube.music"
val COMPATIBLE_PACKAGE: Pair<PackageName, Set<VersionName>?> = Pair(
YOUTUBE_MUSIC_PACKAGE_NAME,
setOf(
"6.20.51", // This is the latest version that supports Android 5.0
"6.29.59", // This is the latest version that supports the 'Restore old player layout' setting.
"6.42.55", // This is the latest version that supports Android 7.0
"6.51.53", // This is the latest version of YouTube Music 6.xx.xx
"7.16.53", // This is the latest version supported by the RVX patch.
)
)
}

View File

@ -0,0 +1,29 @@
package app.revanced.patches.music.utils.extension
@Suppress("MemberVisibilityCanBePrivate")
internal object Constants {
const val EXTENSION_PATH = "Lapp/revanced/extension/music"
const val SHARED_PATH = "$EXTENSION_PATH/shared"
const val PATCHES_PATH = "$EXTENSION_PATH/patches"
const val ACCOUNT_PATH = "$PATCHES_PATH/account"
const val ACTIONBAR_PATH = "$PATCHES_PATH/actionbar"
const val ADS_PATH = "$PATCHES_PATH/ads"
const val COMPONENTS_PATH = "$PATCHES_PATH/components"
const val FLYOUT_PATH = "$PATCHES_PATH/flyout"
const val GENERAL_PATH = "$PATCHES_PATH/general"
const val MISC_PATH = "$PATCHES_PATH/misc"
const val NAVIGATION_PATH = "$PATCHES_PATH/navigation"
const val PLAYER_PATH = "$PATCHES_PATH/player"
const val VIDEO_PATH = "$PATCHES_PATH/video"
const val UTILS_PATH = "$PATCHES_PATH/utils"
const val ACCOUNT_CLASS_DESCRIPTOR = "$ACCOUNT_PATH/AccountPatch;"
const val ACTIONBAR_CLASS_DESCRIPTOR = "$ACTIONBAR_PATH/ActionBarPatch;"
const val FLYOUT_CLASS_DESCRIPTOR = "$FLYOUT_PATH/FlyoutPatch;"
const val GENERAL_CLASS_DESCRIPTOR = "$GENERAL_PATH/GeneralPatch;"
const val NAVIGATION_CLASS_DESCRIPTOR = "$NAVIGATION_PATH/NavigationPatch;"
const val PLAYER_CLASS_DESCRIPTOR = "$PLAYER_PATH/PlayerPatch;"
const val PATCH_STATUS_CLASS_DESCRIPTOR = "$UTILS_PATH/PatchStatus;"
}

View File

@ -0,0 +1,6 @@
package app.revanced.patches.music.utils.extension
import app.revanced.patches.music.utils.extension.hooks.applicationInitHook
import app.revanced.patches.shared.extension.sharedExtensionPatch
val sharedExtensionPatch = sharedExtensionPatch(applicationInitHook)

View File

@ -0,0 +1,20 @@
package app.revanced.patches.music.utils.extension.hooks
import app.revanced.patches.shared.extension.extensionHook
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstruction
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val applicationInitHook = extensionHook {
returns("V")
parameters()
strings("activity")
custom { method, _ ->
method.name == "onCreate" &&
method.indexOfFirstInstruction {
opcode == Opcode.INVOKE_VIRTUAL
&& getReference<MethodReference>()?.name == "getRunningAppProcesses"
} >= 0
}
}

View File

@ -0,0 +1,29 @@
package app.revanced.patches.music.utils.fix.androidauto
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.CERTIFICATE_SPOOF
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.util.fingerprint.methodOrThrow
@Suppress("unused")
val androidAutoCertificatePatch = bytecodePatch(
CERTIFICATE_SPOOF.title,
CERTIFICATE_SPOOF.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
execute {
certificateCheckFingerprint.methodOrThrow().addInstructions(
0,
"""
const/4 v0, 0x1
return v0
""",
)
updatePatchStatus(CERTIFICATE_SPOOF)
}
}

View File

@ -0,0 +1,10 @@
package app.revanced.patches.music.utils.fix.androidauto
import app.revanced.util.fingerprint.legacyFingerprint
internal val certificateCheckFingerprint = legacyFingerprint(
name = "certificateCheckFingerprint",
returnType = "Z",
parameters = listOf("L"),
strings = listOf("X509")
)

View File

@ -0,0 +1,45 @@
package app.revanced.patches.music.utils.fix.fileprovider
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.util.fingerprint.methodOrThrow
fun fileProviderPatch(
youtubePackageName: String,
musicPackageName: String
) = bytecodePatch(
description = "fileProviderPatch"
) {
execute {
/**
* For some reason, if the app gets "android.support.FILE_PROVIDER_PATHS",
* the package name of YouTube is used, not the package name of the YT Music.
*
* There is no issue in the stock YT Music, but this is an issue in the GmsCore Build.
* https://github.com/inotia00/ReVanced_Extended/issues/1830
*
* To solve this issue, replace the package name of YouTube with YT Music's package name.
*/
fileProviderResolverFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
const-string v0, "com.google.android.youtube.fileprovider"
invoke-static {p1, v0}, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
move-result v0
if-nez v0, :fix
const-string v0, "$youtubePackageName.fileprovider"
invoke-static {p1, v0}, Ljava/util/Objects;->equals(Ljava/lang/Object;Ljava/lang/Object;)Z
move-result v0
if-nez v0, :fix
goto :ignore
:fix
const-string p1, "$musicPackageName.fileprovider"
""", ExternalLabel("ignore", getInstruction(0))
)
}
}
}

View File

@ -0,0 +1,12 @@
package app.revanced.patches.music.utils.fix.fileprovider
import app.revanced.util.fingerprint.legacyFingerprint
internal val fileProviderResolverFingerprint = legacyFingerprint(
name = "fileProviderResolverFingerprint",
returnType = "L",
strings = listOf(
"android.support.FILE_PROVIDER_PATHS",
"Name must not be empty"
)
)

View File

@ -0,0 +1,14 @@
package app.revanced.patches.music.utils.flyoutmenu
import app.revanced.patches.music.utils.resourceid.varispeedUnavailableTitle
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val playbackRateBottomSheetClassFingerprint = legacyFingerprint(
name = "playbackRateBottomSheetClassFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
literals = listOf(varispeedUnavailableTitle)
)

View File

@ -0,0 +1,38 @@
package app.revanced.patches.music.utils.flyoutmenu
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.extension.Constants.EXTENSION_PATH
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.util.addStaticFieldToExtension
import app.revanced.util.fingerprint.methodOrThrow
private const val EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR =
"$EXTENSION_PATH/utils/VideoUtils;"
val flyoutMenuHookPatch = bytecodePatch(
description = "flyoutMenuHookPatch",
) {
dependsOn(sharedResourceIdPatch)
execute {
playbackRateBottomSheetClassFingerprint.methodOrThrow().apply {
val smaliInstructions =
"""
if-eqz v0, :ignore
invoke-virtual {v0}, $definingClass->$name()V
:ignore
return-void
"""
addStaticFieldToExtension(
EXTENSION_VIDEO_UTILS_CLASS_DESCRIPTOR,
"showPlaybackSpeedFlyoutMenu",
"playbackRateBottomSheetClass",
definingClass,
smaliInstructions
)
}
}
}

View File

@ -0,0 +1,61 @@
package app.revanced.patches.music.utils.gms
import app.revanced.patcher.patch.Option
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.utils.extension.sharedExtensionPatch
import app.revanced.patches.music.utils.fix.fileprovider.fileProviderPatch
import app.revanced.patches.music.utils.mainactivity.mainActivityFingerprint
import app.revanced.patches.music.utils.patch.PatchList.GMSCORE_SUPPORT
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.addGmsCorePreference
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePackageName
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.gms.gmsCoreSupportPatch
import app.revanced.patches.shared.spoof.useragent.baseSpoofUserAgentPatch
import app.revanced.util.valueOrThrow
@Suppress("unused")
val gmsCoreSupportPatch = gmsCoreSupportPatch(
fromPackageName = YOUTUBE_MUSIC_PACKAGE_NAME,
mainActivityOnCreateFingerprint = mainActivityFingerprint.second,
extensionPatch = sharedExtensionPatch,
gmsCoreSupportResourcePatchFactory = ::gmsCoreSupportResourcePatch,
) {
compatibleWith(COMPATIBLE_PACKAGE)
}
private fun gmsCoreSupportResourcePatch(
gmsCoreVendorGroupIdOption: Option<String>,
packageNameYouTubeOption: Option<String>,
packageNameYouTubeMusicOption: Option<String>,
) = app.revanced.patches.shared.gms.gmsCoreSupportResourcePatch(
fromPackageName = YOUTUBE_MUSIC_PACKAGE_NAME,
spoofedPackageSignature = "afb0fed5eeaebdd86f56a97742f4b6b33ef59875",
gmsCoreVendorGroupIdOption = gmsCoreVendorGroupIdOption,
packageNameYouTubeOption = packageNameYouTubeOption,
packageNameYouTubeMusicOption = packageNameYouTubeMusicOption,
executeBlock = {
updatePackageName(packageNameYouTubeMusicOption.valueOrThrow())
addGmsCorePreference(
CategoryType.MISC.value,
"gms_core_settings",
gmsCoreVendorGroupIdOption.valueOrThrow() + ".android.gms",
"org.microg.gms.ui.SettingsActivity"
)
updatePatchStatus(GMSCORE_SUPPORT)
},
) {
dependsOn(
baseSpoofUserAgentPatch(YOUTUBE_MUSIC_PACKAGE_NAME),
settingsPatch,
fileProviderPatch(
packageNameYouTubeOption.valueOrThrow(),
packageNameYouTubeMusicOption.valueOrThrow()
),
)
}

View File

@ -0,0 +1,16 @@
package app.revanced.patches.music.utils.mainactivity
import app.revanced.util.fingerprint.legacyFingerprint
internal val mainActivityFingerprint = legacyFingerprint(
name = "mainActivityFingerprint",
returnType = "V",
parameters = listOf("Landroid/os/Bundle;"),
strings = listOf(
"android.intent.action.MAIN",
"FEmusic_home"
),
customFingerprint = { method, classDef ->
method.name == "onCreate" && classDef.endsWith("Activity;")
}
)

View File

@ -0,0 +1,5 @@
package app.revanced.patches.music.utils.mainactivity
import app.revanced.patches.shared.mainactivity.baseMainActivityResolvePatch
val mainActivityResolvePatch = baseMainActivityResolvePatch(mainActivityFingerprint)

View File

@ -0,0 +1,156 @@
package app.revanced.patches.music.utils.patch
internal enum class PatchList(
val title: String,
val summary: String,
var included: Boolean? = false
) {
AMOLED(
"Amoled",
"Applies a pure black theme to some components."
),
BITRATE_DEFAULT_VALUE(
"Bitrate default value",
"Sets the audio quality to 'Always High' when you first install the app."
),
BYPASS_IMAGE_REGION_RESTRICTIONS(
"Bypass image region restrictions",
"Adds an option to use a different host for static images, so that images blocked in some countries can be received."
),
CERTIFICATE_SPOOF(
"Certificate spoof",
"Enables YouTube Music to work with Android Auto by spoofing the YouTube Music certificate."
),
CHANGE_SHARE_SHEET(
"Change share sheet",
"Add option to change from in-app share sheet to system share sheet."
),
CHANGE_START_PAGE(
"Change start page",
"Adds an option to set which page the app opens in instead of the homepage."
),
CUSTOM_BRANDING_ICON_FOR_YOUTUBE_MUSIC(
"Custom branding icon for YouTube Music",
"Changes the YouTube Music app icon to the icon specified in patch options."
),
CUSTOM_BRANDING_NAME_FOR_YOUTUBE_MUSIC(
"Custom branding name for YouTube Music",
"Renames the YouTube Music app to the name specified in patch options."
),
CUSTOM_HEADER_FOR_YOUTUBE_MUSIC(
"Custom header for YouTube Music",
"Applies a custom header in the top left corner within the app."
),
DISABLE_CAIRO_SPLASH_ANIMATION(
"Disable Cairo splash animation",
"Adds an option to disable Cairo splash animation."
),
DISABLE_AUTO_CAPTIONS(
"Disable auto captions",
"Adds an option to disable captions from being automatically enabled."
),
DISABLE_DISLIKE_REDIRECTION(
"Disable dislike redirection",
"Adds an option to disable redirection to the next track when clicking the Dislike button."
),
ENABLE_OPUS_CODEC(
"Enable OPUS codec",
"Adds an options to enable the OPUS audio codec if the player response includes."
),
ENABLE_DEBUG_LOGGING(
"Enable debug logging",
"Adds an option to enable debug logging."
),
ENABLE_LANDSCAPE_MODE(
"Enable landscape mode",
"Adds an option to enable landscape mode when rotating the screen on phones."
),
FLYOUT_MENU_COMPONENTS(
"Flyout menu components",
"Adds options to hide or change flyout menu components."
),
GMSCORE_SUPPORT(
"GmsCore support",
"Allows patched Google apps to run without root and under a different package name by using GmsCore instead of Google Play Services."
),
HIDE_ACCOUNT_COMPONENTS(
"Hide account components",
"Adds options to hide components related to the account menu."
),
HIDE_ACTION_BAR_COMPONENTS(
"Hide action bar components",
"Adds options to hide action bar components and replace the offline download button with an external download button."
),
HIDE_ADS(
"Hide ads",
"Adds options to hide ads."
),
HIDE_LAYOUT_COMPONENTS(
"Hide layout components",
"Adds options to hide general layout components."
),
HIDE_OVERLAY_FILTER(
"Hide overlay filter",
"Removes, at compile time, the dark overlay that appears when player flyout menus are open."
),
HIDE_PLAYER_OVERLAY_FILTER(
"Hide player overlay filter",
"Removes, at compile time, the dark overlay that appears when single-tapping in the player."
),
NAVIGATION_BAR_COMPONENTS(
"Navigation bar components",
"Adds options to hide or change components related to the navigation bar."
),
PLAYER_COMPONENTS(
"Player components",
"Adds options to hide or change components related to the player."
),
REMOVE_BACKGROUND_PLAYBACK_RESTRICTIONS(
"Remove background playback restrictions",
"Removes restrictions on background playback, including for kids videos."
),
REMOVE_VIEWER_DISCRETION_DIALOG(
"Remove viewer discretion dialog",
"Adds an option to remove the dialog that appears when opening a video that has been age-restricted by accepting it automatically. This does not bypass the age restriction."
),
RESTORE_OLD_STYLE_LIBRARY_SHELF(
"Restore old style library shelf",
"Adds an option to return the Library tab to the old style."
),
RETURN_YOUTUBE_DISLIKE(
"Return YouTube Dislike",
"Adds an option to show the dislike count of songs using the Return YouTube Dislike API."
),
RETURN_YOUTUBE_USERNAME(
"Return YouTube Username",
"Adds an option to replace YouTube handles with usernames in comments using YouTube Data API v3."
),
SANITIZE_SHARING_LINKS(
"Sanitize sharing links",
"Adds an option to remove tracking query parameters from URLs when sharing links."
),
SETTINGS_FOR_YOUTUBE_MUSIC(
"Settings for YouTube Music",
"Applies mandatory patches to implement ReVanced Extended settings into the application."
),
SPONSORBLOCK(
"SponsorBlock",
"Adds options to enable and configure SponsorBlock, which can skip undesired video segments, such as non-music sections."
),
SPOOF_APP_VERSION(
"Spoof app version",
"Adds options to spoof the YouTube Music client version. This can remove the radio mode restriction in Canadian regions or disable real-time lyrics."
),
TRANSLATIONS_FOR_YOUTUBE_MUSIC(
"Translations for YouTube Music",
"Add translations or remove string resources."
),
VIDEO_PLAYBACK(
"Video playback",
"Adds options to customize settings related to video playback, such as default video quality and playback speed."
),
VISUAL_PREFERENCES_ICONS_FOR_YOUTUBE_MUSIC(
"Visual preferences icons for YouTube Music",
"Adds icons to specific preferences in the settings."
)
}

View File

@ -0,0 +1,22 @@
package app.revanced.patches.music.utils.playertype
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val playerTypeFingerprint = legacyFingerprint(
name = "playerTypeFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.IGET_BOOLEAN,
Opcode.IF_NEZ,
Opcode.IPUT_OBJECT,
Opcode.RETURN_VOID
),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MppWatchWhileLayout;")
}
)

View File

@ -0,0 +1,24 @@
package app.revanced.patches.music.utils.playertype
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.util.fingerprint.methodOrThrow
private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/PlayerTypeHookPatch;"
@Suppress("unused")
val playerTypeHookPatch = bytecodePatch(
description = "playerTypeHookPatch"
) {
execute {
playerTypeFingerprint.methodOrThrow().addInstruction(
0,
"invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->setPlayerType(Ljava/lang/Enum;)V"
)
}
}

View File

@ -0,0 +1,45 @@
@file:Suppress("ktlint:standard:property-naming")
package app.revanced.patches.music.utils.playservice
import app.revanced.patcher.patch.resourcePatch
import app.revanced.util.findElementByAttributeValueOrThrow
var is_6_27_or_greater = false
private set
var is_6_36_or_greater = false
private set
var is_6_42_or_greater = false
private set
var is_7_06_or_greater = false
private set
var is_7_18_or_greater = false
private set
var is_7_20_or_greater = false
private set
var is_7_23_or_greater = false
private set
val versionCheckPatch = resourcePatch(
description = "versionCheckPatch",
) {
execute {
// The app version is missing from the decompiled manifest,
// so instead use the Google Play services version and compare against specific releases.
val playStoreServicesVersion = document("res/values/integers.xml").use { document ->
document.documentElement.childNodes.findElementByAttributeValueOrThrow(
"name",
"google_play_services_version",
).textContent.toInt()
}
// All bug fix releases always seem to use the same play store version as the minor version.
is_6_27_or_greater = 234412000 <= playStoreServicesVersion
is_6_36_or_greater = 240399000 <= playStoreServicesVersion
is_6_42_or_greater = 240999000 <= playStoreServicesVersion
is_7_06_or_greater = 242499000 <= playStoreServicesVersion
is_7_18_or_greater = 243699000 <= playStoreServicesVersion
is_7_20_or_greater = 243899000 <= playStoreServicesVersion
is_7_23_or_greater = 244199000 <= playStoreServicesVersion
}
}

View File

@ -0,0 +1,269 @@
package app.revanced.patches.music.utils.resourceid
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.shared.mapping.ResourceType.BOOL
import app.revanced.patches.shared.mapping.ResourceType.COLOR
import app.revanced.patches.shared.mapping.ResourceType.DIMEN
import app.revanced.patches.shared.mapping.ResourceType.ID
import app.revanced.patches.shared.mapping.ResourceType.LAYOUT
import app.revanced.patches.shared.mapping.ResourceType.STRING
import app.revanced.patches.shared.mapping.ResourceType.STYLE
import app.revanced.patches.shared.mapping.get
import app.revanced.patches.shared.mapping.resourceMappingPatch
import app.revanced.patches.shared.mapping.resourceMappings
var accountSwitcherAccessibility = -1L
private set
var bottomSheetRecyclerView = -1L
private set
var buttonContainer = -1L
private set
var buttonIconPaddingMedium = -1L
private set
var chipCloud = -1L
private set
var colorGrey = -1L
private set
var darkBackground = -1L
private set
var designBottomSheetDialog = -1L
private set
var endButtonsContainer = -1L
private set
var floatingLayout = -1L
private set
var historyMenuItem = -1L
private set
var inlineTimeBarAdBreakMarkerColor = -1L
private set
var interstitialsContainer = -1L
private set
var isTablet = -1L
private set
var likeDislikeContainer = -1L
private set
var mainActivityLaunchAnimation = -1L
private set
var menuEntry = -1L
private set
var miniPlayerDefaultText = -1L
private set
var miniPlayerMdxPlaying = -1L
private set
var miniPlayerPlayPauseReplayButton = -1L
private set
var miniPlayerViewPager = -1L
private set
var musicNotifierShelf = -1L
private set
var musicTasteBuilderShelf = -1L
private set
var namesInactiveAccountThumbnailSize = -1L
private set
var offlineSettingsMenuItem = -1L
private set
var playerOverlayChip = -1L
private set
var playerViewPager = -1L
private set
var privacyTosFooter = -1L
private set
var qualityAuto = -1L
private set
var remixGenericButtonSize = -1L
private set
var slidingDialogAnimation = -1L
private set
var tapBloomView = -1L
private set
var text1 = -1L
private set
var toolTipContentView = -1L
private set
var topEnd = -1L
private set
var topStart = -1L
private set
var topBarMenuItemImageView = -1L
private set
var tosFooter = -1L
private set
var touchOutside = -1L
private set
var trimSilenceSwitch = -1L
private set
var varispeedUnavailableTitle = -1L
private set
internal val sharedResourceIdPatch = resourcePatch(
description = "sharedResourceIdPatch"
) {
dependsOn(resourceMappingPatch)
execute {
accountSwitcherAccessibility = resourceMappings[
STRING,
"account_switcher_accessibility_label",
]
bottomSheetRecyclerView = resourceMappings[
LAYOUT,
"bottom_sheet_recycler_view"
]
buttonContainer = resourceMappings[
ID,
"button_container"
]
buttonIconPaddingMedium = resourceMappings[
DIMEN,
"button_icon_padding_medium"
]
chipCloud = resourceMappings[
LAYOUT,
"chip_cloud"
]
colorGrey = resourceMappings[
COLOR,
"ytm_color_grey_12"
]
darkBackground = resourceMappings[
ID,
"dark_background"
]
designBottomSheetDialog = resourceMappings[
LAYOUT,
"design_bottom_sheet_dialog"
]
endButtonsContainer = resourceMappings[
ID,
"end_buttons_container"
]
floatingLayout = resourceMappings[
ID,
"floating_layout"
]
historyMenuItem = resourceMappings[
ID,
"history_menu_item"
]
inlineTimeBarAdBreakMarkerColor = resourceMappings[
COLOR,
"inline_time_bar_ad_break_marker_color"
]
interstitialsContainer = resourceMappings[
ID,
"interstitials_container"
]
isTablet = resourceMappings[
BOOL,
"is_tablet"
]
likeDislikeContainer = resourceMappings[
ID,
"like_dislike_container"
]
mainActivityLaunchAnimation = resourceMappings[
LAYOUT,
"main_activity_launch_animation"
]
menuEntry = resourceMappings[
LAYOUT,
"menu_entry"
]
miniPlayerDefaultText = resourceMappings[
STRING,
"mini_player_default_text"
]
miniPlayerMdxPlaying = resourceMappings[
STRING,
"mini_player_mdx_playing"
]
miniPlayerPlayPauseReplayButton = resourceMappings[
ID,
"mini_player_play_pause_replay_button"
]
miniPlayerViewPager = resourceMappings[
ID,
"mini_player_view_pager"
]
musicNotifierShelf = resourceMappings[
LAYOUT,
"music_notifier_shelf"
]
musicTasteBuilderShelf = resourceMappings[
LAYOUT,
"music_tastebuilder_shelf"
]
namesInactiveAccountThumbnailSize = resourceMappings[
DIMEN,
"names_inactive_account_thumbnail_size"
]
offlineSettingsMenuItem = resourceMappings[
ID,
"offline_settings_menu_item"
]
playerOverlayChip = resourceMappings[
ID,
"player_overlay_chip"
]
playerViewPager = resourceMappings[
ID,
"player_view_pager"
]
privacyTosFooter = resourceMappings[
ID,
"privacy_tos_footer"
]
qualityAuto = resourceMappings[
STRING,
"quality_auto"
]
remixGenericButtonSize = resourceMappings[
DIMEN,
"remix_generic_button_size"
]
slidingDialogAnimation = resourceMappings[
STYLE,
"SlidingDialogAnimation"
]
tapBloomView = resourceMappings[
ID,
"tap_bloom_view"
]
text1 = resourceMappings[
ID,
"text1"
]
toolTipContentView = resourceMappings[
LAYOUT,
"tooltip_content_view"
]
topEnd = resourceMappings[
ID,
"TOP_END"
]
topStart = resourceMappings[
ID,
"TOP_START"
]
topBarMenuItemImageView = resourceMappings[
ID,
"top_bar_menu_item_image_view"
]
tosFooter = resourceMappings[
ID,
"tos_footer"
]
touchOutside = resourceMappings[
ID,
"touch_outside"
]
trimSilenceSwitch = resourceMappings[
ID,
"trim_silence_switch"
]
varispeedUnavailableTitle = resourceMappings[
STRING,
"varispeed_unavailable_title"
]
}
}

View File

@ -0,0 +1,12 @@
package app.revanced.patches.music.utils.returnyoutubedislike
import app.revanced.patches.music.utils.resourceid.buttonIconPaddingMedium
import app.revanced.util.fingerprint.legacyFingerprint
import com.android.tools.smali.dexlib2.Opcode
internal val textComponentFingerprint = legacyFingerprint(
name = "textComponentFingerprint",
returnType = "V",
opcodes = listOf(Opcode.CONST_HIGH16),
literals = listOf(buttonIconPaddingMedium),
)

View File

@ -0,0 +1,159 @@
package app.revanced.patches.music.utils.returnyoutubedislike
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.music.utils.patch.PatchList.RETURN_YOUTUBE_DISLIKE
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.PREFERENCE_CATEGORY_TAG_NAME
import app.revanced.patches.music.utils.settings.ResourceUtils.SETTINGS_HEADER_PATH
import app.revanced.patches.music.utils.settings.ResourceUtils.addPreferenceCategoryUnderPreferenceScreen
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.shared.dislikeFingerprint
import app.revanced.patches.shared.likeFingerprint
import app.revanced.patches.shared.removeLikeFingerprint
import app.revanced.util.adoptChild
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/ReturnYouTubeDislikePatch;"
private val returnYouTubeDislikeBytecodePatch = bytecodePatch(
description = "returnYouTubeDislikeBytecodePatch"
) {
dependsOn(
settingsPatch,
sharedResourceIdPatch,
videoInformationPatch
)
execute {
mapOf(
likeFingerprint to Vote.LIKE,
dislikeFingerprint to Vote.DISLIKE,
removeLikeFingerprint to Vote.REMOVE_LIKE,
).forEach { (fingerprint, vote) ->
fingerprint.methodOrThrow().addInstructions(
0,
"""
const/4 v0, ${vote.value}
invoke-static {v0}, $EXTENSION_CLASS_DESCRIPTOR->sendVote(I)V
""",
)
}
textComponentFingerprint.methodOrThrow().apply {
val insertIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_STATIC
&& (this as ReferenceInstruction).reference.toString()
.endsWith("Ljava/lang/CharSequence;")
} + 2
val insertRegister =
getInstruction<OneRegisterInstruction>(insertIndex - 1).registerA
addInstructions(
insertIndex, """
invoke-static {v$insertRegister}, $EXTENSION_CLASS_DESCRIPTOR->onSpannedCreated(Landroid/text/Spanned;)Landroid/text/Spanned;
move-result-object v$insertRegister
"""
)
}
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V")
}
}
enum class Vote(val value: Int) {
LIKE(1),
DISLIKE(-1),
REMOVE_LIKE(0),
}
private const val ABOUT_CATEGORY_KEY = "revanced_ryd_about"
private const val RYD_ATTRIBUTION_KEY = "revanced_ryd_attribution"
@Suppress("unused")
val returnYouTubeDislikePatch = resourcePatch(
RETURN_YOUTUBE_DISLIKE.title,
RETURN_YOUTUBE_DISLIKE.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
returnYouTubeDislikeBytecodePatch,
settingsPatch,
)
execute {
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_DISLIKE,
"revanced_ryd_enabled",
"true"
)
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_DISLIKE,
"revanced_ryd_dislike_percentage",
"false",
"revanced_ryd_enabled"
)
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_DISLIKE,
"revanced_ryd_compact_layout",
"false",
"revanced_ryd_enabled"
)
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_DISLIKE,
"revanced_ryd_estimated_like",
"false",
"revanced_ryd_enabled"
)
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_DISLIKE,
"revanced_ryd_toast_on_connection_error",
"false",
"revanced_ryd_enabled"
)
addPreferenceCategoryUnderPreferenceScreen(
CategoryType.RETURN_YOUTUBE_DISLIKE.value,
ABOUT_CATEGORY_KEY
)
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_CATEGORY_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(ABOUT_CATEGORY_KEY) }
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/$RYD_ATTRIBUTION_KEY" + "_title")
setAttribute("android:summary", "@string/$RYD_ATTRIBUTION_KEY" + "_summary")
setAttribute("android:key", RYD_ATTRIBUTION_KEY)
this.adoptChild("intent") {
setAttribute("android:action", "android.intent.action.VIEW")
setAttribute("android:data", "https://returnyoutubedislike.com")
}
}
}
}
updatePatchStatus(RETURN_YOUTUBE_DISLIKE)
}
}

View File

@ -0,0 +1,55 @@
package app.revanced.patches.music.utils.returnyoutubeusername
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.patch.PatchList.RETURN_YOUTUBE_USERNAME
import app.revanced.patches.music.utils.playservice.is_6_42_or_greater
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.shared.returnyoutubeusername.baseReturnYouTubeUsernamePatch
@Suppress("unused")
val returnYouTubeUsernamePatch = resourcePatch(
RETURN_YOUTUBE_USERNAME.title,
RETURN_YOUTUBE_USERNAME.summary,
use = false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
baseReturnYouTubeUsernamePatch,
settingsPatch,
versionCheckPatch,
)
execute {
addSwitchPreference(
CategoryType.RETURN_YOUTUBE_USERNAME,
"revanced_return_youtube_username_enabled",
"false"
)
addPreferenceWithIntent(
CategoryType.RETURN_YOUTUBE_USERNAME,
"revanced_return_youtube_username_display_format",
"revanced_return_youtube_username_enabled"
)
addPreferenceWithIntent(
CategoryType.RETURN_YOUTUBE_USERNAME,
"revanced_return_youtube_username_youtube_data_api_v3_developer_key",
"revanced_return_youtube_username_enabled"
)
if (is_6_42_or_greater) {
addPreferenceWithIntent(
CategoryType.RETURN_YOUTUBE_USERNAME,
"revanced_return_youtube_username_youtube_data_api_v3_about"
)
}
updatePatchStatus(RETURN_YOUTUBE_USERNAME)
}
}

View File

@ -0,0 +1,17 @@
package app.revanced.patches.music.utils.settings
internal enum class CategoryType(val value: String, var added: Boolean) {
ACCOUNT("account", false),
ACTION_BAR("action_bar", false),
ADS("ads", false),
FLYOUT("flyout", false),
GENERAL("general", false),
NAVIGATION("navigation", false),
PLAYER("player", false),
SETTINGS("settings", false),
VIDEO("video", false),
RETURN_YOUTUBE_DISLIKE("ryd", false),
RETURN_YOUTUBE_USERNAME("return_youtube_username", false),
SPONSOR_BLOCK("sb", false),
MISC("misc", false)
}

View File

@ -0,0 +1,47 @@
package app.revanced.patches.music.utils.settings
import app.revanced.util.fingerprint.legacyFingerprint
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val googleApiActivityFingerprint = legacyFingerprint(
name = "googleApiActivityFingerprint",
returnType = "V",
parameters = listOf("Landroid/os/Bundle;"),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/GoogleApiActivity;") &&
method.name == "onCreate"
}
)
internal val preferenceFingerprint = legacyFingerprint(
name = "preferenceFingerprint",
accessFlags = AccessFlags.PROTECTED.value,
returnType = "V",
parameters = listOf("Z"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IGET_OBJECT,
Opcode.INVOKE_INTERFACE,
),
customFingerprint = { method, _ ->
method.definingClass == "Landroidx/preference/Preference;"
}
)
internal val settingsHeadersFragmentFingerprint = legacyFingerprint(
name = "settingsHeadersFragmentFingerprint",
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/SettingsHeadersFragment;") &&
method.name == "onCreate"
}
)

View File

@ -0,0 +1,259 @@
package app.revanced.patches.music.utils.settings
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patches.music.utils.compatibility.Constants.YOUTUBE_MUSIC_PACKAGE_NAME
import app.revanced.patches.music.utils.patch.PatchList
import app.revanced.util.adoptChild
import app.revanced.util.cloneNodes
import app.revanced.util.doRecursively
import app.revanced.util.insertNode
import org.w3c.dom.Element
internal object ResourceUtils {
private lateinit var context: ResourcePatchContext
fun setContext(context: ResourcePatchContext) {
this.context = context
}
private const val RVX_SETTINGS_KEY = "revanced_extended_settings"
const val SETTINGS_HEADER_PATH = "res/xml/settings_headers.xml"
const val PREFERENCE_SCREEN_TAG_NAME =
"PreferenceScreen"
const val PREFERENCE_CATEGORY_TAG_NAME =
"com.google.android.apps.youtube.music.ui.preference.PreferenceCategoryCompat"
const val SWITCH_PREFERENCE_TAG_NAME =
"com.google.android.apps.youtube.music.ui.preference.SwitchCompatPreference"
const val ACTIVITY_HOOK_TARGET_CLASS =
"com.google.android.gms.common.api.GoogleApiActivity"
var musicPackageName = YOUTUBE_MUSIC_PACKAGE_NAME
private var iconType = "default"
fun getIconType() = iconType
fun setIconType(iconName: String) {
iconType = iconName
}
private fun isIncludedCategory(category: String): Boolean {
CategoryType.entries.forEach { preference ->
if (category == preference.value)
return preference.added
}
return false
}
private fun replacePackageName() = context.apply {
val xmlFile = get(SETTINGS_HEADER_PATH)
xmlFile.writeText(
xmlFile.readText()
.replace(
"\"com.google.android.apps.youtube.music\"",
"\"" + musicPackageName + "\""
)
)
}
private fun setPreferenceCategory(newCategory: String) {
CategoryType.entries.forEach { preference ->
if (newCategory == preference.value)
preference.added = true
}
}
fun updatePackageName(newPackage: String) {
musicPackageName = newPackage
replacePackageName()
}
fun updatePatchStatus(patch: PatchList) {
patch.included = true
}
fun addPreferenceCategory(category: String) {
context.document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(RVX_SETTINGS_KEY) }
.forEach {
if (!isIncludedCategory(category)) {
it.adoptChild(PREFERENCE_SCREEN_TAG_NAME) {
setAttribute(
"android:title",
"@string/revanced_preference_screen_$category" + "_title"
)
setAttribute("android:key", "revanced_preference_screen_$category")
}
setPreferenceCategory(category)
}
}
}
}
fun addPreferenceCategoryUnderPreferenceScreen(
preferenceScreenKey: String,
category: String
) {
context.document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(preferenceScreenKey) }
.forEach {
it.adoptChild(PREFERENCE_CATEGORY_TAG_NAME) {
setAttribute("android:title", "@string/$category")
setAttribute("android:key", category)
}
}
}
}
fun sortPreferenceCategory(
category: String
) {
context.document(SETTINGS_HEADER_PATH).use { document ->
document.doRecursively node@{
if (it !is Element) return@node
it.getAttributeNode("android:key")?.let { attribute ->
if (attribute.textContent == "revanced_preference_screen_$category") {
it.cloneNodes(it.parentNode)
}
}
}
}
replacePackageName()
}
fun addGmsCorePreference(
category: String,
key: String,
packageName: String,
targetClassName: String
) {
context.document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter {
it.getAttribute("android:key").contains("revanced_preference_screen_$category")
}
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/$key" + "_title")
setAttribute("android:summary", "@string/$key" + "_summary")
this.adoptChild("intent") {
setAttribute("android:targetPackage", packageName)
setAttribute("android:data", key)
setAttribute(
"android:targetClass",
targetClassName
)
}
}
}
}
}
fun addSwitchPreference(
category: String,
key: String,
defaultValue: String,
dependencyKey: String,
setSummary: Boolean
) {
context.document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter {
it.getAttribute("android:key").contains("revanced_preference_screen_$category")
}
.forEach {
it.adoptChild(SWITCH_PREFERENCE_TAG_NAME) {
setAttribute("android:title", "@string/$key" + "_title")
if (setSummary) {
setAttribute("android:summary", "@string/$key" + "_summary")
}
setAttribute("android:key", key)
setAttribute("android:defaultValue", defaultValue)
if (dependencyKey != "") {
setAttribute("android:dependency", dependencyKey)
}
}
}
}
}
fun addPreferenceWithIntent(
category: String,
key: String,
dependencyKey: String
) {
context.document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter {
it.getAttribute("android:key").contains("revanced_preference_screen_$category")
}
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/$key" + "_title")
setAttribute("android:summary", "@string/$key" + "_summary")
setAttribute("android:key", key)
if (dependencyKey != "") {
setAttribute("android:dependency", dependencyKey)
}
this.adoptChild("intent") {
setAttribute("android:targetPackage", musicPackageName)
setAttribute("android:data", key)
setAttribute(
"android:targetClass",
ACTIVITY_HOOK_TARGET_CLASS
)
}
}
}
}
}
fun addRVXSettingsPreference() {
context.document(SETTINGS_HEADER_PATH).use { document ->
document.doRecursively node@{
if (it !is Element) return@node
it.getAttributeNode("android:key")?.let { attribute ->
if (attribute.textContent == "settings_header_about_youtube_music" && it.getAttributeNode(
"app:allowDividerBelow"
).textContent == "false"
) {
it.insertNode(PREFERENCE_SCREEN_TAG_NAME, it) {
setAttribute(
"android:title",
"@string/revanced_extended_settings_title"
)
setAttribute("android:key", "revanced_extended_settings")
setAttribute("app:allowDividerAbove", "false")
}
it.getAttributeNode("app:allowDividerBelow").textContent = "true"
return@node
}
}
}
document.doRecursively node@{
if (it !is Element) return@node
it.getAttributeNode("app:allowDividerBelow")?.let { attribute ->
if (attribute.textContent == "true") {
attribute.textContent = "false"
}
}
}
}
}
}

View File

@ -0,0 +1,306 @@
package app.revanced.patches.music.utils.settings
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.EXTENSION_PATH
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.patches.music.utils.extension.sharedExtensionPatch
import app.revanced.patches.music.utils.mainactivity.mainActivityResolvePatch
import app.revanced.patches.music.utils.patch.PatchList.SETTINGS_FOR_YOUTUBE_MUSIC
import app.revanced.patches.music.utils.playservice.versionCheckPatch
import app.revanced.patches.shared.extension.Constants.EXTENSION_UTILS_CLASS_DESCRIPTOR
import app.revanced.patches.shared.mainactivity.injectConstructorMethodCall
import app.revanced.patches.shared.mainactivity.injectOnCreateMethodCall
import app.revanced.patches.shared.sharedSettingFingerprint
import app.revanced.util.copyXmlNode
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.removeStringsElements
import app.revanced.util.valueOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import org.w3c.dom.Element
private const val EXTENSION_ACTIVITY_CLASS_DESCRIPTOR =
"$EXTENSION_PATH/settings/ActivityHook;"
private const val EXTENSION_FRAGMENT_CLASS_DESCRIPTOR =
"$EXTENSION_PATH/settings/preference/ReVancedPreferenceFragment;"
private const val EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR =
"$UTILS_PATH/InitializationPatch;"
private val settingsBytecodePatch = bytecodePatch(
description = "settingsBytecodePatch"
) {
dependsOn(
sharedExtensionPatch,
mainActivityResolvePatch,
versionCheckPatch,
)
execute {
// region patch for set SharedPrefCategory
sharedSettingFingerprint.methodOrThrow().apply {
val stringIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_STRING)
val stringRegister = getInstruction<OneRegisterInstruction>(stringIndex).registerA
replaceInstruction(
stringIndex,
"const-string v$stringRegister, \"youtube\""
)
}
// endregion
// region patch for hook activity
settingsHeadersFragmentFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.endIndex
val targetRegister = getInstruction<OneRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $EXTENSION_ACTIVITY_CLASS_DESCRIPTOR->setActivity(Ljava/lang/Object;)V"
)
}
}
// endregion
// region patch for hook preference change listener
preferenceFingerprint.matchOrThrow().let {
it.method.apply {
val targetIndex = it.patternMatch!!.endIndex
val keyRegister = getInstruction<FiveRegisterInstruction>(targetIndex).registerD
val valueRegister = getInstruction<FiveRegisterInstruction>(targetIndex).registerE
addInstruction(
targetIndex,
"invoke-static {v$keyRegister, v$valueRegister}, $EXTENSION_FRAGMENT_CLASS_DESCRIPTOR->onPreferenceChanged(Ljava/lang/String;Z)V"
)
}
}
// endregion
// region patch for hook dummy Activity for intent
googleApiActivityFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
1,
"""
invoke-static {p0}, $EXTENSION_ACTIVITY_CLASS_DESCRIPTOR->initialize(Landroid/app/Activity;)Z
move-result v0
if-eqz v0, :show
return-void
""",
ExternalLabel("show", getInstruction(1)),
)
}
// endregion
injectOnCreateMethodCall(
EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR,
"setDeviceInformation"
)
injectOnCreateMethodCall(
EXTENSION_INITIALIZATION_CLASS_DESCRIPTOR,
"onCreate"
)
injectConstructorMethodCall(
EXTENSION_UTILS_CLASS_DESCRIPTOR,
"setActivity"
)
}
}
private const val DEFAULT_LABEL = "ReVanced Extended"
private lateinit var customName: String
val settingsPatch = resourcePatch(
SETTINGS_FOR_YOUTUBE_MUSIC.title,
SETTINGS_FOR_YOUTUBE_MUSIC.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
settingsBytecodePatch,
)
val settingsLabel = stringOption(
key = "settingsLabel",
default = DEFAULT_LABEL,
title = "RVX settings label",
description = "The name of the RVX settings menu.",
required = true,
)
execute {
/**
* check patch options
*/
customName = settingsLabel
.valueOrThrow()
/**
* copy arrays, colors and strings
*/
arrayOf(
"arrays.xml",
"colors.xml",
"strings.xml"
).forEach { xmlFile ->
copyXmlNode("music/settings/host", "values/$xmlFile", "resources")
}
/**
* hide divider
*/
val styleFile = get("res/values/styles.xml")
styleFile.writeText(
styleFile.readText()
.replace(
"allowDividerAbove\">true",
"allowDividerAbove\">false"
).replace(
"allowDividerBelow\">true",
"allowDividerBelow\">false"
)
)
/**
* Change colors
*/
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")) {
"material_deep_teal_500",
-> "@android:color/white"
else -> continue
}
}
}
ResourceUtils.setContext(this)
ResourceUtils.addRVXSettingsPreference()
ResourceUtils.updatePatchStatus(SETTINGS_FOR_YOUTUBE_MUSIC)
}
finalize {
/**
* change RVX settings menu name
* since it must be invoked after the Translations patch, it must be the last in the order.
*/
if (customName != DEFAULT_LABEL) {
removeStringsElements(
arrayOf("revanced_extended_settings_title")
)
document("res/values/strings.xml").use { document ->
mapOf(
"revanced_extended_settings_title" to customName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0)
.appendChild(stringElement)
}
}
}
/**
* add open default app settings
*/
addPreferenceWithIntent(
CategoryType.MISC,
"revanced_default_app_settings"
)
/**
* add import export settings
*/
addPreferenceWithIntent(
CategoryType.MISC,
"revanced_extended_settings_import_export"
)
/**
* sort preference
*/
CategoryType.entries.sorted().forEach {
ResourceUtils.sortPreferenceCategory(it.value)
}
}
}
internal fun addSwitchPreference(
category: CategoryType,
key: String,
defaultValue: String
) = addSwitchPreference(category, key, defaultValue, "")
internal fun addSwitchPreference(
category: CategoryType,
key: String,
defaultValue: String,
setSummary: Boolean
) = addSwitchPreference(category, key, defaultValue, "", setSummary)
internal fun addSwitchPreference(
category: CategoryType,
key: String,
defaultValue: String,
dependencyKey: String
) = addSwitchPreference(category, key, defaultValue, dependencyKey, true)
internal fun addSwitchPreference(
category: CategoryType,
key: String,
defaultValue: String,
dependencyKey: String,
setSummary: Boolean
) {
val categoryValue = category.value
ResourceUtils.addPreferenceCategory(categoryValue)
ResourceUtils.addSwitchPreference(categoryValue, key, defaultValue, dependencyKey, setSummary)
}
internal fun addPreferenceWithIntent(
category: CategoryType,
key: String
) = addPreferenceWithIntent(category, key, "")
internal fun addPreferenceWithIntent(
category: CategoryType,
key: String,
dependencyKey: String
) {
val categoryValue = category.value
ResourceUtils.addPreferenceCategory(categoryValue)
ResourceUtils.addPreferenceWithIntent(categoryValue, key, dependencyKey)
}

View File

@ -0,0 +1,64 @@
package app.revanced.patches.music.utils.sponsorblock
import app.revanced.patches.music.utils.resourceid.inlineTimeBarAdBreakMarkerColor
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionReversed
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.Method
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
internal val musicPlaybackControlsTimeBarDrawFingerprint = legacyFingerprint(
name = "musicPlaybackControlsTimeBarDrawFingerprint",
returnType = "V",
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicPlaybackControlsTimeBar;") &&
method.name == "draw"
}
)
internal val musicPlaybackControlsTimeBarOnMeasureFingerprint = legacyFingerprint(
name = "musicPlaybackControlsTimeBarOnMeasureFingerprint",
returnType = "V",
opcodes = listOf(
Opcode.IGET_OBJECT,
Opcode.INVOKE_VIRTUAL,
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/MusicPlaybackControlsTimeBar;") &&
method.name == "onMeasure"
}
)
internal val rectangleFieldInvalidatorFingerprint = legacyFingerprint(
name = "rectangleFieldInvalidatorFingerprint",
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_WIDE,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_WIDE,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_WIDE
),
customFingerprint = { method, _ ->
indexOfInvalidateInstruction(method) >= 0
}
)
internal fun indexOfInvalidateInstruction(method: Method) =
method.indexOfFirstInstructionReversed {
getReference<MethodReference>()?.name == "invalidate"
}
internal val seekBarConstructorFingerprint = legacyFingerprint(
name = "seekBarConstructorFingerprint",
returnType = "V",
literals = listOf(inlineTimeBarAdBreakMarkerColor),
)
internal val seekbarOnDrawFingerprint = legacyFingerprint(
name = "seekbarOnDrawFingerprint",
customFingerprint = { method, _ -> method.name == "onDraw" }
)

View File

@ -0,0 +1,395 @@
package app.revanced.patches.music.utils.sponsorblock
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.ResourcePatchContext
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.EXTENSION_PATH
import app.revanced.patches.music.utils.patch.PatchList.SPONSORBLOCK
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.ACTIVITY_HOOK_TARGET_CLASS
import app.revanced.patches.music.utils.settings.ResourceUtils.PREFERENCE_CATEGORY_TAG_NAME
import app.revanced.patches.music.utils.settings.ResourceUtils.PREFERENCE_SCREEN_TAG_NAME
import app.revanced.patches.music.utils.settings.ResourceUtils.SETTINGS_HEADER_PATH
import app.revanced.patches.music.utils.settings.ResourceUtils.SWITCH_PREFERENCE_TAG_NAME
import app.revanced.patches.music.utils.settings.ResourceUtils.addPreferenceCategory
import app.revanced.patches.music.utils.settings.ResourceUtils.musicPackageName
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.music.video.information.videoTimeHook
import app.revanced.util.adoptChild
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import org.w3c.dom.Element
private const val EXTENSION_CLASS_DESCRIPTOR =
"$EXTENSION_PATH/sponsorblock/SegmentPlaybackController;"
private val sponsorBlockBytecodePatch = bytecodePatch(
description = "sponsorBlockBytecodePatch"
) {
dependsOn(
sharedResourceIdPatch,
videoInformationPatch
)
execute {
/**
* Hook the video time methods & Initialize the player controller
*/
videoTimeHook(EXTENSION_CLASS_DESCRIPTOR, "setVideoTime")
/**
* Responsible for seekbar in fullscreen
*/
var rectangleFieldName =
with(rectangleFieldInvalidatorFingerprint.methodOrThrow(seekBarConstructorFingerprint)) {
val invalidateIndex = indexOfInvalidateInstruction(this)
val rectangleIndex =
indexOfFirstInstructionReversedOrThrow(invalidateIndex + 1) {
getReference<FieldReference>()?.type == "Landroid/graphics/Rect;"
}
val rectangleReference =
getInstruction<ReferenceInstruction>(rectangleIndex).reference
(rectangleReference as FieldReference).name
}
seekbarOnDrawFingerprint.methodOrThrow(seekBarConstructorFingerprint).apply {
// Initialize seekbar method
addInstructions(
0, """
move-object/from16 v0, p0
const-string v1, "$rectangleFieldName"
invoke-static {v0, v1}, $EXTENSION_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;Ljava/lang/String;)V
"""
)
// Set seekbar thickness
val roundIndex = indexOfFirstInstructionOrThrow {
getReference<MethodReference>()?.name == "round"
} + 1
val roundRegister = getInstruction<OneRegisterInstruction>(roundIndex).registerA
addInstruction(
roundIndex + 1,
"invoke-static {v$roundRegister}, " +
"$EXTENSION_CLASS_DESCRIPTOR->setSponsorBarThickness(I)V"
)
// Draw segment
val drawCircleIndex = indexOfFirstInstructionReversedOrThrow {
getReference<MethodReference>()?.name == "drawCircle"
}
val drawCircleInstruction = getInstruction<FiveRegisterInstruction>(drawCircleIndex)
addInstruction(
drawCircleIndex,
"invoke-static {v${drawCircleInstruction.registerC}, v${drawCircleInstruction.registerE}}, " +
"$EXTENSION_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V"
)
}
/**
* Responsible for seekbar in player
*/
rectangleFieldName =
musicPlaybackControlsTimeBarOnMeasureFingerprint.matchOrThrow().let {
with(it.method) {
val rectangleIndex = it.patternMatch!!.startIndex
val rectangleReference =
getInstruction<ReferenceInstruction>(rectangleIndex).reference
(rectangleReference as FieldReference).name
}
}
musicPlaybackControlsTimeBarDrawFingerprint.methodOrThrow().apply {
// Initialize seekbar method
addInstructions(
1, """
move-object/from16 v0, p0
const-string v1, "$rectangleFieldName"
invoke-static {v0, v1}, $EXTENSION_CLASS_DESCRIPTOR->setSponsorBarRect(Ljava/lang/Object;Ljava/lang/String;)V
"""
)
// Draw segment
val drawCircleIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.name == "drawCircle"
}
val drawCircleInstruction = getInstruction<FiveRegisterInstruction>(drawCircleIndex)
addInstruction(
drawCircleIndex,
"invoke-static {v${drawCircleInstruction.registerC}, v${drawCircleInstruction.registerE}}, " +
"$EXTENSION_CLASS_DESCRIPTOR->drawSponsorTimeBars(Landroid/graphics/Canvas;F)V"
)
}
/**
* Set current video id
*/
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
}
}
private const val SEGMENTS_CATEGORY_KEY = "sb_diff_segments"
private const val ABOUT_CATEGORY_KEY = "sb_about"
private val SPONSOR_BLOCK_CATEGORY = CategoryType.SPONSOR_BLOCK.value
@Suppress("unused")
val sponsorBlockPatch = resourcePatch(
SPONSORBLOCK.title,
SPONSORBLOCK.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
sponsorBlockBytecodePatch,
settingsPatch,
)
execute {
addPreferenceCategory(SPONSOR_BLOCK_CATEGORY)
addSwitchPreference(
SPONSOR_BLOCK_CATEGORY,
"sb_enabled",
"true"
)
addSwitchPreference(
SPONSOR_BLOCK_CATEGORY,
"sb_toast_on_skip",
"true",
"sb_enabled"
)
addSwitchPreference(
SPONSOR_BLOCK_CATEGORY,
"sb_toast_on_connection_error",
"false",
"sb_enabled"
)
addPreferenceWithIntent(
SPONSOR_BLOCK_CATEGORY,
"sb_api_url",
"sb_enabled"
)
addPreferenceCategoryUnderPreferenceScreen(
SPONSOR_BLOCK_CATEGORY,
SEGMENTS_CATEGORY_KEY
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_sponsor",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_selfpromo",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_interaction",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_intro",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_outro",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_preview",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_filler",
"sb_enabled"
)
addSegmentsPreference(
SEGMENTS_CATEGORY_KEY,
"sb_segments_nomusic",
"sb_enabled"
)
addPreferenceCategoryUnderPreferenceScreen(
CategoryType.SPONSOR_BLOCK.value,
ABOUT_CATEGORY_KEY
)
addAboutPreference(
ABOUT_CATEGORY_KEY,
"sb_about_api",
"https://sponsor.ajay.app"
)
get(SETTINGS_HEADER_PATH).apply {
writeText(
readText()
.replace(
"\"sb_segments_nomusic",
"\"sb_segments_music_offtopic"
)
)
}
updatePatchStatus(SPONSORBLOCK)
}
}
private fun ResourcePatchContext.addSwitchPreference(
category: String,
key: String,
defaultValue: String
) = addSwitchPreference(category, key, defaultValue, "")
private fun ResourcePatchContext.addSwitchPreference(
category: String,
key: String,
defaultValue: String,
dependencyKey: String
) {
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter {
it.getAttribute("android:key").contains("revanced_preference_screen_$category")
}
.forEach {
it.adoptChild(SWITCH_PREFERENCE_TAG_NAME) {
setAttribute("android:title", "@string/revanced_$key")
setAttribute("android:summary", "@string/revanced_$key" + "_sum")
setAttribute("android:key", key)
setAttribute("android:defaultValue", defaultValue)
if (dependencyKey != "") {
setAttribute("android:dependency", dependencyKey)
}
}
}
}
}
private fun ResourcePatchContext.addPreferenceWithIntent(
category: String,
key: String,
dependencyKey: String
) {
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter {
it.getAttribute("android:key").contains("revanced_preference_screen_$category")
}
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/revanced_$key")
setAttribute("android:summary", "@string/revanced_$key" + "_sum")
setAttribute("android:key", key)
setAttribute("android:dependency", dependencyKey)
this.adoptChild("intent") {
setAttribute("android:targetPackage", musicPackageName)
setAttribute("android:data", key)
setAttribute(
"android:targetClass",
ACTIVITY_HOOK_TARGET_CLASS
)
}
}
}
}
}
private fun ResourcePatchContext.addPreferenceCategoryUnderPreferenceScreen(
preferenceScreenKey: String,
category: String
) {
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(preferenceScreenKey) }
.forEach {
it.adoptChild(PREFERENCE_CATEGORY_TAG_NAME) {
setAttribute("android:title", "@string/revanced_$category")
setAttribute("android:key", category)
}
}
}
}
private fun ResourcePatchContext.addSegmentsPreference(
preferenceCategoryKey: String,
key: String,
dependencyKey: String
) {
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(preferenceCategoryKey) }
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/revanced_$key")
setAttribute("android:summary", "@string/revanced_$key" + "_sum")
setAttribute("android:key", key)
setAttribute("android:dependency", dependencyKey)
this.adoptChild("intent") {
setAttribute("android:targetPackage", musicPackageName)
setAttribute("android:data", key)
setAttribute(
"android:targetClass",
ACTIVITY_HOOK_TARGET_CLASS
)
}
}
}
}
}
private fun ResourcePatchContext.addAboutPreference(
preferenceCategoryKey: String,
key: String,
data: String
) {
document(SETTINGS_HEADER_PATH).use { document ->
val tags = document.getElementsByTagName(PREFERENCE_SCREEN_TAG_NAME)
List(tags.length) { tags.item(it) as Element }
.filter { it.getAttribute("android:key").contains(preferenceCategoryKey) }
.forEach {
it.adoptChild("Preference") {
setAttribute("android:title", "@string/revanced_$key")
setAttribute("android:summary", "@string/revanced_$key" + "_sum")
setAttribute("android:key", key)
this.adoptChild("intent") {
setAttribute("android:action", "android.intent.action.VIEW")
setAttribute("android:data", data)
}
}
}
}
}

View File

@ -0,0 +1,30 @@
package app.revanced.patches.music.utils.videotype
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val videoTypeFingerprint = legacyFingerprint(
name = "videoTypeFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L"),
opcodes = listOf(
Opcode.IGET,
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_NEZ,
Opcode.SGET_OBJECT,
Opcode.GOTO,
Opcode.SGET_OBJECT
)
)
internal val videoTypeParentFingerprint = legacyFingerprint(
name = "videoTypeParentFingerprint",
returnType = "Z",
accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC,
parameters = listOf("L", "L"),
strings = listOf("RQ")
)

View File

@ -0,0 +1,38 @@
package app.revanced.patches.music.utils.videotype
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.extension.Constants.UTILS_PATH
import app.revanced.util.fingerprint.matchOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
private const val EXTENSION_CLASS_DESCRIPTOR =
"$UTILS_PATH/VideoTypeHookPatch;"
@Suppress("unused")
val videoTypeHookPatch = bytecodePatch(
description = "videoTypeHookPatch"
) {
execute {
videoTypeFingerprint.matchOrThrow(videoTypeParentFingerprint).let {
it.method.apply {
val insertIndex = it.patternMatch!!.startIndex + 3
val referenceIndex = insertIndex + 1
val referenceInstruction =
getInstruction<ReferenceInstruction>(referenceIndex).reference
addInstructionsWithLabels(
insertIndex, """
if-nez p0, :dismiss
sget-object p0, $referenceInstruction
:dismiss
invoke-static {p0}, $EXTENSION_CLASS_DESCRIPTOR->setVideoType(Ljava/lang/Enum;)V
"""
)
}
}
}
}

View File

@ -0,0 +1,62 @@
package app.revanced.patches.music.video.information
import app.revanced.patches.music.utils.resourceid.qualityAuto
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val playerControllerSetTimeReferenceFingerprint = legacyFingerprint(
name = "playerControllerSetTimeReferenceFingerprint",
returnType = "V",
opcodes = listOf(
Opcode.INVOKE_DIRECT_RANGE,
Opcode.IGET_OBJECT
),
strings = listOf("Media progress reported outside media playback: ")
)
internal val videoEndFingerprint = legacyFingerprint(
name = "videoEndFingerprint",
strings = listOf("Attempting to seek during an ad")
)
internal val videoIdFingerprint = legacyFingerprint(
name = "videoIdFingerprint",
returnType = "V",
parameters = listOf("L", "Ljava/lang/String;"),
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(
Opcode.INVOKE_INTERFACE_RANGE,
Opcode.MOVE_RESULT_OBJECT,
Opcode.INVOKE_INTERFACE_RANGE,
Opcode.MOVE_RESULT_OBJECT,
),
strings = listOf("Null initialPlayabilityStatus")
)
internal val videoQualityListFingerprint = legacyFingerprint(
name = "videoQualityListFingerprint",
returnType = "V",
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_INTERFACE,
Opcode.RETURN_VOID
),
literals = listOf(qualityAuto)
)
internal val videoQualityTextFingerprint = legacyFingerprint(
name = "videoQualityTextFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("[L", "I", "Z"),
opcodes = listOf(
Opcode.IF_EQZ,
Opcode.IF_LTZ,
Opcode.ARRAY_LENGTH,
Opcode.IF_GE,
Opcode.AGET_OBJECT,
Opcode.IGET_OBJECT
)
)

View File

@ -0,0 +1,392 @@
package app.revanced.patches.music.video.information
import app.revanced.patcher.Fingerprint
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.proxy.mutableTypes.MutableClass
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod
import app.revanced.patcher.util.proxy.mutableTypes.MutableMethod.Companion.toMutable
import app.revanced.patcher.util.smali.toInstructions
import app.revanced.patches.music.utils.extension.Constants.SHARED_PATH
import app.revanced.patches.music.utils.playbackSpeedFingerprint
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
import app.revanced.patches.music.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.shared.mdxPlayerDirectorSetVideoStageFingerprint
import app.revanced.patches.shared.videoLengthFingerprint
import app.revanced.util.addStaticFieldToExtension
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
import com.android.tools.smali.dexlib2.immutable.ImmutableMethod
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodImplementation
import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter
private const val EXTENSION_CLASS_DESCRIPTOR =
"$SHARED_PATH/VideoInformation;"
private const val REGISTER_PLAYER_RESPONSE_MODEL = 4
private const val REGISTER_VIDEO_ID = 0
private const val REGISTER_VIDEO_LENGTH = 1
@Suppress("unused")
private const val REGISTER_VIDEO_LENGTH_DUMMY = 2
private lateinit var PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR: String
private lateinit var videoIdMethodCall: String
private lateinit var videoLengthMethodCall: String
private lateinit var videoInformationMethod: MutableMethod
/**
* Used in [videoEndFingerprint] and [mdxPlayerDirectorSetVideoStageFingerprint].
* Since both classes are inherited from the same class,
* [videoEndFingerprint] and [mdxPlayerDirectorSetVideoStageFingerprint] always have the same [seekSourceEnumType] and [seekSourceMethodName].
*/
private var seekSourceEnumType = ""
private var seekSourceMethodName = ""
private lateinit var playerConstructorMethod: MutableMethod
private var playerConstructorInsertIndex = -1
private lateinit var mdxConstructorMethod: MutableMethod
private var mdxConstructorInsertIndex = -1
private lateinit var videoTimeConstructorMethod: MutableMethod
private var videoTimeConstructorInsertIndex = 2
val videoInformationPatch = bytecodePatch(
description = "videoInformationPatch",
) {
dependsOn(sharedResourceIdPatch)
execute {
fun addSeekInterfaceMethods(
targetClass: MutableClass,
targetMethod: MutableMethod,
seekMethodName: String,
methodName: String,
fieldName: String
) {
targetMethod.apply {
targetClass.methods.add(
ImmutableMethod(
definingClass,
"seekTo",
listOf(ImmutableMethodParameter("J", annotations, "time")),
"Z",
AccessFlags.PUBLIC or AccessFlags.FINAL,
annotations,
null,
ImmutableMethodImplementation(
4, """
# first enum (field a) is SEEK_SOURCE_UNKNOWN
sget-object v0, $seekSourceEnumType->a:$seekSourceEnumType
invoke-virtual {p0, p1, p2, v0}, $definingClass->$seekMethodName(J$seekSourceEnumType)Z
move-result p1
return p1
""".toInstructions(),
null,
null
)
).toMutable()
)
val smaliInstructions =
"""
if-eqz v0, :ignore
invoke-virtual {v0, p0, p1}, $definingClass->seekTo(J)Z
move-result v0
return v0
:ignore
const/4 v0, 0x0
return v0
"""
addStaticFieldToExtension(
EXTENSION_CLASS_DESCRIPTOR,
methodName,
fieldName,
definingClass,
smaliInstructions
)
}
}
fun Pair<String, Fingerprint>.getPlayerResponseInstruction(returnType: String): String {
methodOrThrow().apply {
val targetReference = getInstruction<ReferenceInstruction>(
indexOfFirstInstructionOrThrow {
val reference = getReference<MethodReference>()
(opcode == Opcode.INVOKE_INTERFACE_RANGE || opcode == Opcode.INVOKE_INTERFACE) &&
reference?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR &&
reference.returnType == returnType
}
).reference
return "invoke-interface {v$REGISTER_PLAYER_RESPONSE_MODEL}, $targetReference"
}
}
videoEndFingerprint.methodOrThrow().apply {
findMethodOrThrow(definingClass).let {
playerConstructorMethod = it
playerConstructorInsertIndex = it.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT && getReference<MethodReference>()?.name == "<init>"
} + 1
}
// hook the player controller for use through extension
onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "initialize")
seekSourceEnumType = parameterTypes[1].toString()
seekSourceMethodName = name
// Create extension interface methods.
addSeekInterfaceMethods(
videoEndFingerprint.mutableClassOrThrow(),
this,
seekSourceMethodName,
"overrideVideoTime",
"videoInformationClass"
)
}
mdxPlayerDirectorSetVideoStageFingerprint.methodOrThrow().apply {
findMethodOrThrow(definingClass).let {
mdxConstructorMethod = it
mdxConstructorInsertIndex = it.indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_DIRECT && getReference<MethodReference>()?.name == "<init>"
} + 1
}
// hook the MDX director for use through extension
onCreateHookMdx(EXTENSION_CLASS_DESCRIPTOR, "initializeMdx")
// Create extension interface methods.
addSeekInterfaceMethods(
mdxPlayerDirectorSetVideoStageFingerprint.mutableClassOrThrow(),
this,
seekSourceMethodName,
"overrideMDXVideoTime",
"videoInformationMDXClass"
)
}
/**
* Set current video information
*/
videoIdFingerprint.matchOrThrow().let {
it.method.apply {
val playerResponseModelIndex = it.patternMatch!!.startIndex
PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR =
getInstruction(playerResponseModelIndex)
.getReference<MethodReference>()
?.definingClass
?: throw PatchException("Could not find Player Response Model class")
videoIdMethodCall =
videoIdFingerprint.getPlayerResponseInstruction("Ljava/lang/String;")
videoLengthMethodCall =
videoLengthFingerprint.getPlayerResponseInstruction("J")
videoInformationMethod = getVideoInformationMethod()
it.classDef.methods.add(videoInformationMethod)
addInstruction(
playerResponseModelIndex + 2,
"invoke-direct/range {p0 .. p1}, $definingClass->setVideoInformation($PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR)V"
)
}
}
/**
* Set the video time method
*/
playerControllerSetTimeReferenceFingerprint.matchOrThrow().let {
videoTimeConstructorMethod =
it.getWalkerMethod(it.patternMatch!!.startIndex)
}
/**
* Set current video time
*/
videoTimeHook(EXTENSION_CLASS_DESCRIPTOR, "setVideoTime")
/**
* Set current video length
*/
videoLengthHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoLength(J)V")
/**
* Set current video id
*/
videoIdHook("$EXTENSION_CLASS_DESCRIPTOR->setVideoId(Ljava/lang/String;)V")
/**
* Hook current playback speed
*/
playbackSpeedFingerprint.matchOrThrow(playbackSpeedParentFingerprint).let {
it.getWalkerMethod(it.patternMatch!!.endIndex).apply {
addInstruction(
implementation!!.instructions.lastIndex,
"invoke-static {p1}, $EXTENSION_CLASS_DESCRIPTOR->setPlaybackSpeed(F)V"
)
}
}
/**
* Hook current video quality
*/
videoQualityListFingerprint.matchOrThrow().let {
it.method.apply {
val videoQualityMethodName =
findMethodOrThrow(definingClass) { parameterTypes.first() == "I" }.name
// set video quality array
val listIndex = it.patternMatch!!.startIndex
val listRegister = getInstruction<FiveRegisterInstruction>(listIndex).registerD
addInstruction(
listIndex,
"invoke-static {v$listRegister}, $EXTENSION_CLASS_DESCRIPTOR->setVideoQualityList([Ljava/lang/Object;)V"
)
val smaliInstructions =
"""
if-eqz v0, :ignore
invoke-virtual {v0, p0}, $definingClass->$videoQualityMethodName(I)V
:ignore
return-void
"""
addStaticFieldToExtension(
EXTENSION_CLASS_DESCRIPTOR,
"overrideVideoQuality",
"videoQualityClass",
definingClass,
smaliInstructions
)
}
}
// set current video quality
videoQualityTextFingerprint.matchOrThrow().let {
it.method.apply {
val textIndex = it.patternMatch!!.endIndex
val textRegister = getInstruction<TwoRegisterInstruction>(textIndex).registerA
addInstruction(
textIndex + 1,
"invoke-static {v$textRegister}, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality(Ljava/lang/String;)V"
)
}
}
}
}
private fun MutableMethod.getVideoInformationMethod(): MutableMethod =
ImmutableMethod(
definingClass,
"setVideoInformation",
listOf(
ImmutableMethodParameter(
PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR,
annotations,
null
)
),
"V",
AccessFlags.PRIVATE or AccessFlags.FINAL,
annotations,
null,
ImmutableMethodImplementation(
REGISTER_PLAYER_RESPONSE_MODEL + 1, """
$videoIdMethodCall
move-result-object v$REGISTER_VIDEO_ID
$videoLengthMethodCall
move-result-wide v$REGISTER_VIDEO_LENGTH
return-void
""".toInstructions(),
null,
null
)
).toMutable()
private fun MutableMethod.insert(insertIndex: Int, register: String, descriptor: String) =
addInstruction(insertIndex, "invoke-static { $register }, $descriptor")
private fun MutableMethod.insertTimeHook(insertIndex: Int, descriptor: String) =
insert(insertIndex, "p1, p2", descriptor)
/**
* Hook the player controller. Called when a video is opened or the current video is changed.
*
* Note: This hook is called very early and is called before the video id, video time, video length,
* and many other data fields are set.
*
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun onCreateHook(targetMethodClass: String, targetMethodName: String) =
playerConstructorMethod.addInstruction(
playerConstructorInsertIndex++,
"invoke-static { }, $targetMethodClass->$targetMethodName()V"
)
/**
* Hook the MDX player director. Called when playing videos while casting to a big screen device.
*
* @param targetMethodClass The descriptor for the class to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun onCreateHookMdx(targetMethodClass: String, targetMethodName: String) =
mdxConstructorMethod.addInstruction(
mdxConstructorInsertIndex++,
"invoke-static { }, $targetMethodClass->$targetMethodName()V"
)
internal fun videoIdHook(
descriptor: String
) = videoInformationMethod.apply {
addInstruction(
implementation!!.instructions.lastIndex,
"invoke-static {v$REGISTER_VIDEO_ID}, $descriptor"
)
}
internal fun videoLengthHook(
descriptor: String
) = videoInformationMethod.apply {
addInstruction(
implementation!!.instructions.lastIndex,
"invoke-static {v$REGISTER_VIDEO_LENGTH, v$REGISTER_VIDEO_LENGTH_DUMMY}, $descriptor"
)
}
/**
* Hook the video time.
* The hook is usually called once per second.
*
* @param targetMethodClass The descriptor for the static method to invoke when the player controller is created.
* @param targetMethodName The name of the static method to invoke when the player controller is created.
*/
internal fun videoTimeHook(targetMethodClass: String, targetMethodName: String) =
videoTimeConstructorMethod.insertTimeHook(
videoTimeConstructorInsertIndex++,
"$targetMethodClass->$targetMethodName(J)V"
)

View File

@ -0,0 +1,27 @@
package app.revanced.patches.music.video.playback
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val playbackSpeedBottomSheetFingerprint = legacyFingerprint(
name = "playbackSpeedBottomSheetFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
strings = listOf("PLAYBACK_RATE_MENU_BOTTOM_SHEET_FRAGMENT")
)
internal val userQualityChangeFingerprint = legacyFingerprint(
name = "userQualityChangeFingerprint",
returnType = "V",
opcodes = listOf(
Opcode.CONST_STRING,
Opcode.INVOKE_VIRTUAL,
Opcode.MOVE_RESULT_OBJECT,
Opcode.IF_EQZ,
Opcode.CHECK_CAST
),
strings = listOf("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT")
)

View File

@ -0,0 +1,138 @@
package app.revanced.patches.music.video.playback
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.PatchException
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.music.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.music.utils.extension.Constants.VIDEO_PATH
import app.revanced.patches.music.utils.patch.PatchList.VIDEO_PLAYBACK
import app.revanced.patches.music.utils.playbackSpeedFingerprint
import app.revanced.patches.music.utils.playbackSpeedParentFingerprint
import app.revanced.patches.music.utils.settings.CategoryType
import app.revanced.patches.music.utils.settings.ResourceUtils.updatePatchStatus
import app.revanced.patches.music.utils.settings.addPreferenceWithIntent
import app.revanced.patches.music.utils.settings.addSwitchPreference
import app.revanced.patches.music.utils.settings.settingsPatch
import app.revanced.patches.music.video.information.videoIdHook
import app.revanced.patches.music.video.information.videoInformationPatch
import app.revanced.patches.shared.customspeed.customPlaybackSpeedPatch
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.mutableClassOrThrow
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
private const val EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR =
"$VIDEO_PATH/PlaybackSpeedPatch;"
private const val EXTENSION_VIDEO_QUALITY_CLASS_DESCRIPTOR =
"$VIDEO_PATH/VideoQualityPatch;"
@Suppress("unused")
val videoPlaybackPatch = bytecodePatch(
VIDEO_PLAYBACK.title,
VIDEO_PLAYBACK.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
customPlaybackSpeedPatch(
"$VIDEO_PATH/CustomPlaybackSpeedPatch;",
5.0f
),
settingsPatch,
videoInformationPatch,
)
execute {
// region patch for default playback speed
playbackSpeedBottomSheetFingerprint.mutableClassOrThrow().let {
val onItemClickMethod =
it.methods.find { method -> method.name == "onItemClick" }
onItemClickMethod?.apply {
val targetIndex = indexOfFirstInstructionOrThrow(Opcode.IGET)
val targetRegister =
getInstruction<TwoRegisterInstruction>(targetIndex).registerA
addInstruction(
targetIndex + 1,
"invoke-static {v$targetRegister}, $EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->userSelectedPlaybackSpeed(F)V"
)
} ?: throw PatchException("Failed to find onItemClick method")
}
playbackSpeedFingerprint.matchOrThrow(playbackSpeedParentFingerprint).let {
it.method.apply {
val startIndex = it.patternMatch!!.startIndex
val speedRegister =
getInstruction<OneRegisterInstruction>(startIndex + 1).registerA
addInstructions(
startIndex + 2, """
invoke-static {v$speedRegister}, $EXTENSION_PLAYBACK_SPEED_CLASS_DESCRIPTOR->getPlaybackSpeed(F)F
move-result v$speedRegister
"""
)
}
}
// endregion
// region patch for default video quality
userQualityChangeFingerprint.matchOrThrow().let {
it.method.apply {
val endIndex = it.patternMatch!!.endIndex
val qualityChangedClass =
getInstruction<ReferenceInstruction>(endIndex).reference.toString()
findMethodOrThrow(qualityChangedClass) {
name == "onItemClick"
}.addInstruction(
0,
"invoke-static {}, $EXTENSION_VIDEO_QUALITY_CLASS_DESCRIPTOR->userSelectedVideoQuality()V"
)
}
}
videoIdHook("$EXTENSION_VIDEO_QUALITY_CLASS_DESCRIPTOR->newVideoStarted(Ljava/lang/String;)V")
// endregion
addPreferenceWithIntent(
CategoryType.VIDEO,
"revanced_custom_playback_speeds"
)
addSwitchPreference(
CategoryType.VIDEO,
"revanced_remember_playback_speed_last_selected",
"true"
)
addSwitchPreference(
CategoryType.VIDEO,
"revanced_remember_playback_speed_last_selected_toast",
"true",
"revanced_remember_playback_speed_last_selected"
)
addSwitchPreference(
CategoryType.VIDEO,
"revanced_remember_video_quality_last_selected",
"true"
)
addSwitchPreference(
CategoryType.VIDEO,
"revanced_remember_video_quality_last_selected_toast",
"true",
"revanced_remember_video_quality_last_selected"
)
updatePatchStatus(VIDEO_PLAYBACK)
}
}

View File

@ -0,0 +1,134 @@
package app.revanced.patches.reddit.ad
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.extensions.InstructionExtensions.replaceInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.HIDE_ADS
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.matchOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.getWalkerMethod
import app.revanced.util.indexOfFirstInstructionOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.TwoRegisterInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
import com.android.tools.smali.dexlib2.iface.reference.MethodReference
private const val RESOURCE_FILE_PATH = "res/layout/merge_listheader_link_detail.xml"
private val bannerAdsPatch = resourcePatch(
description = "bannerAdsPatch",
) {
execute {
document(RESOURCE_FILE_PATH).use { document ->
document.getElementsByTagName("merge").item(0).childNodes.apply {
val attributes = arrayOf("height", "width")
for (i in 1 until length) {
val view = item(i)
if (
view.hasAttributes() &&
view.attributes.getNamedItem("android:id").nodeValue.endsWith("ad_view_stub")
) {
attributes.forEach { attribute ->
view.attributes.getNamedItem("android:layout_$attribute").nodeValue =
"0.0dip"
}
break
}
}
}
}
}
}
private const val EXTENSION_METHOD_DESCRIPTOR =
"$PATCHES_PATH/GeneralAdsPatch;->hideCommentAds()Z"
private val commentAdsPatch = bytecodePatch(
description = "commentAdsPatch",
) {
execute {
commentAdsFingerprint.matchOrThrow().let {
val walkerMethod = it.getWalkerMethod(it.patternMatch!!.startIndex)
walkerMethod.apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
move-result v0
if-eqz v0, :show
new-instance v0, Ljava/lang/Object;
invoke-direct {v0}, Ljava/lang/Object;-><init>()V
return-object v0
""", ExternalLabel("show", getInstruction(0))
)
}
}
}
}
private const val EXTENSION_CLASS_DESCRIPTOR =
"$PATCHES_PATH/GeneralAdsPatch;"
@Suppress("unused")
val adsPatch = bytecodePatch(
HIDE_ADS.title,
HIDE_ADS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
bannerAdsPatch,
commentAdsPatch,
settingsPatch
)
execute {
// region Filter promoted ads (does not work in popular or latest feed)
adPostFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstInstructionOrThrow {
getReference<FieldReference>()?.name == "children"
}
val targetRegister = getInstruction<TwoRegisterInstruction>(targetIndex).registerA
addInstructions(
targetIndex, """
invoke-static {v$targetRegister}, $EXTENSION_CLASS_DESCRIPTOR->hideOldPostAds(Ljava/util/List;)Ljava/util/List;
move-result-object v$targetRegister
"""
)
}
// The new feeds work by inserting posts into lists.
// AdElementConverter is conveniently responsible for inserting all feed ads.
// By removing the appending instruction no ad posts gets appended to the feed.
newAdPostFingerprint.methodOrThrow().apply {
val targetIndex = indexOfFirstInstructionOrThrow {
opcode == Opcode.INVOKE_VIRTUAL &&
getReference<MethodReference>()?.toString() == "Ljava/util/ArrayList;->add(Ljava/lang/Object;)Z"
}
val targetInstruction = getInstruction<FiveRegisterInstruction>(targetIndex)
replaceInstruction(
targetIndex,
"invoke-static {v${targetInstruction.registerC}, v${targetInstruction.registerD}}, " +
"$EXTENSION_CLASS_DESCRIPTOR->hideNewPostAds(Ljava/util/ArrayList;Ljava/lang/Object;)V"
)
}
updatePatchStatus(
"enableGeneralAds",
HIDE_ADS
)
}
}

View File

@ -0,0 +1,54 @@
package app.revanced.patches.reddit.ad
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val commentAdsFingerprint = legacyFingerprint(
name = "commentAdsFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = listOf("L"),
opcodes = listOf(
Opcode.INVOKE_STATIC,
Opcode.MOVE_RESULT_OBJECT,
Opcode.RETURN_OBJECT
),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/PostDetailPresenter\$loadAd\$1;") &&
method.name == "invokeSuspend"
},
)
internal val adPostFingerprint = legacyFingerprint(
name = "adPostFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.CONSTRUCTOR,
opcodes = listOf(
Opcode.INVOKE_DIRECT,
Opcode.IPUT_OBJECT
),
// "children" are present throughout multiple versions
strings = listOf(
"children",
"uxExperiences"
),
customFingerprint = { method, classDef ->
method.definingClass.endsWith("/Listing;") &&
method.name == "<init>" &&
classDef.sourceFile == "Listing.kt"
},
)
internal val newAdPostFingerprint = legacyFingerprint(
name = "newAdPostFingerprint",
returnType = "L",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
opcodes = listOf(Opcode.INVOKE_VIRTUAL),
strings = listOf(
"chain",
"feedElement"
),
customFingerprint = { _, classDef -> classDef.sourceFile == "AdElementConverter.kt" },
)

View File

@ -0,0 +1,73 @@
package app.revanced.patches.reddit.layout.branding.name
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.patch.PatchList.CUSTOM_BRANDING_NAME_FOR_REDDIT
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.valueOrThrow
import java.io.FileWriter
import java.nio.file.Files
private const val ORIGINAL_APP_NAME = "Reddit"
private const val APP_NAME = "RVX Reddit"
@Suppress("unused")
val customBrandingNamePatch = resourcePatch(
CUSTOM_BRANDING_NAME_FOR_REDDIT.title,
CUSTOM_BRANDING_NAME_FOR_REDDIT.summary,
false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
val appNameOption = stringOption(
key = "appName",
default = ORIGINAL_APP_NAME,
values = mapOf(
"Default" to APP_NAME,
"Original" to ORIGINAL_APP_NAME,
),
title = "App name",
description = "The name of the app.",
required = true
)
execute {
val appName = appNameOption
.valueOrThrow()
if (appName == ORIGINAL_APP_NAME) {
println("INFO: App name will remain unchanged as it matches the original.")
return@execute
}
val resDirectory = get("res")
val valuesV24Directory = resDirectory.resolve("values-v24")
if (!valuesV24Directory.isDirectory)
Files.createDirectories(valuesV24Directory.toPath())
val stringsXml = valuesV24Directory.resolve("strings.xml")
if (!stringsXml.exists()) {
FileWriter(stringsXml).use {
it.write("<?xml version=\"1.0\" encoding=\"utf-8\"?><resources></resources>")
}
}
document("res/values-v24/strings.xml").use { document ->
mapOf(
"app_name" to appName
).forEach { (k, v) ->
val stringElement = document.createElement("string")
stringElement.setAttribute("name", k)
stringElement.textContent = v
document.getElementsByTagName("resources").item(0).appendChild(stringElement)
}
}
updatePatchStatus(CUSTOM_BRANDING_NAME_FOR_REDDIT)
}
}

View File

@ -0,0 +1,110 @@
package app.revanced.patches.reddit.layout.branding.packagename
import app.revanced.patcher.patch.resourcePatch
import app.revanced.patcher.patch.stringOption
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.patch.PatchList.CHANGE_PACKAGE_NAME
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.valueOrThrow
import org.w3c.dom.Element
private const val PACKAGE_NAME_REDDIT = "com.reddit.frontpage"
private const val CLONE_PACKAGE_NAME_REDDIT = "$PACKAGE_NAME_REDDIT.revanced"
private const val DEFAULT_PACKAGE_NAME_REDDIT = "$PACKAGE_NAME_REDDIT.rvx"
private var redditPackageName = PACKAGE_NAME_REDDIT
@Suppress("unused")
val changePackageNamePatch = resourcePatch(
CHANGE_PACKAGE_NAME.title,
CHANGE_PACKAGE_NAME.summary,
false,
) {
compatibleWith(COMPATIBLE_PACKAGE)
val packageNameRedditOption = stringOption(
key = "packageNameReddit",
default = PACKAGE_NAME_REDDIT,
values = mapOf(
"Clone" to CLONE_PACKAGE_NAME_REDDIT,
"Default" to DEFAULT_PACKAGE_NAME_REDDIT,
"Original" to PACKAGE_NAME_REDDIT,
),
title = "Package name of Reddit",
description = "The name of the package to rename the app to.",
required = true
)
execute {
fun replacePackageName() {
// replace strings
document("res/values/strings.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")) {
"provider_authority_appdata", "provider_authority_file",
"provider_authority_userdata", "provider_workmanager_init"
-> node.textContent.replace(PACKAGE_NAME_REDDIT, redditPackageName)
else -> continue
}
}
}
// replace manifest permission and provider
get("AndroidManifest.xml").apply {
writeText(
readText()
.replace(
"android:authorities=\"$PACKAGE_NAME_REDDIT",
"android:authorities=\"$redditPackageName"
)
)
}
}
redditPackageName = packageNameRedditOption
.valueOrThrow()
if (redditPackageName == PACKAGE_NAME_REDDIT) {
println("INFO: Package name will remain unchanged as it matches the original.")
return@execute
}
// Ensure device runs Android.
try {
// RVX Manager
// ====
// For some reason, in Android AAPT2, a compilation error occurs when changing the [strings.xml] of the Reddit
// This only affects RVX Manager, and has not yet found a valid workaround
Class.forName("android.os.Environment")
} catch (_: ClassNotFoundException) {
// CLI
replacePackageName()
}
updatePatchStatus(CHANGE_PACKAGE_NAME)
}
finalize {
if (redditPackageName != PACKAGE_NAME_REDDIT) {
get("AndroidManifest.xml").apply {
writeText(
readText()
.replace(
"package=\"$PACKAGE_NAME_REDDIT",
"package=\"$redditPackageName"
)
.replace(
"$PACKAGE_NAME_REDDIT.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION",
"$redditPackageName.DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION"
)
)
}
}
}
}

View File

@ -0,0 +1,14 @@
package app.revanced.patches.reddit.layout.communities
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val communityRecommendationSectionFingerprint = legacyFingerprint(
name = "communityRecommendationSectionFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
customFingerprint = { method, _ ->
method.definingClass.endsWith("/CommunityRecommendationSection;")
}
)

View File

@ -0,0 +1,44 @@
package app.revanced.patches.reddit.layout.communities
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.HIDE_RECOMMENDED_COMMUNITIES_SHELF
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.methodOrThrow
private const val EXTENSION_METHOD_DESCRIPTOR =
"$PATCHES_PATH/RecommendedCommunitiesPatch;->hideRecommendedCommunitiesShelf()Z"
@Suppress("unused")
val recommendedCommunitiesPatch = bytecodePatch(
HIDE_RECOMMENDED_COMMUNITIES_SHELF.title,
HIDE_RECOMMENDED_COMMUNITIES_SHELF.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
communityRecommendationSectionFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0,
"""
invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
move-result v0
if-eqz v0, :off
return-void
""", ExternalLabel("off", getInstruction(0))
)
}
updatePatchStatus(
"enableRecommendedCommunitiesShelf",
HIDE_RECOMMENDED_COMMUNITIES_SHELF
)
}
}

View File

@ -0,0 +1,21 @@
package app.revanced.patches.reddit.layout.navigation
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val bottomNavScreenFingerprint = legacyFingerprint(
name = "bottomNavScreenFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(
Opcode.INVOKE_VIRTUAL,
Opcode.RETURN_VOID
),
customFingerprint = { method, classDef ->
method.name == "onGlobalLayout" &&
classDef.type.startsWith("Lcom/reddit/launch/bottomnav/BottomNavScreen\$")
}
)

View File

@ -0,0 +1,45 @@
package app.revanced.patches.reddit.layout.navigation
import app.revanced.patcher.extensions.InstructionExtensions.addInstruction
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.HIDE_NAVIGATION_BUTTONS
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.matchOrThrow
import com.android.tools.smali.dexlib2.iface.instruction.FiveRegisterInstruction
private const val EXTENSION_METHOD_DESCRIPTOR =
"$PATCHES_PATH/NavigationButtonsPatch;->hideNavigationButtons(Landroid/view/ViewGroup;)V"
@Suppress("unused")
val navigationButtonsPatch = bytecodePatch(
HIDE_NAVIGATION_BUTTONS.title,
HIDE_NAVIGATION_BUTTONS.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
bottomNavScreenFingerprint.matchOrThrow().let {
it.method.apply {
val startIndex = it.patternMatch!!.startIndex
val targetRegister =
getInstruction<FiveRegisterInstruction>(startIndex).registerC
addInstruction(
startIndex + 1,
"invoke-static {v$targetRegister}, $EXTENSION_METHOD_DESCRIPTOR"
)
}
}
updatePatchStatus(
"enableNavigationButtons",
HIDE_NAVIGATION_BUTTONS
)
}
}

View File

@ -0,0 +1,13 @@
package app.revanced.patches.reddit.layout.premiumicon
import app.revanced.util.fingerprint.legacyFingerprint
internal val premiumIconFingerprint = legacyFingerprint(
name = "premiumIconFingerprint",
returnType = "Z",
customFingerprint = { method, classDef ->
method.definingClass.endsWith("/MyAccount;") &&
method.name == "isPremiumSubscriber" &&
classDef.sourceFile == "MyAccount.kt"
}
)

View File

@ -0,0 +1,27 @@
package app.revanced.patches.reddit.layout.premiumicon
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.patch.PatchList.PREMIUM_ICON
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.methodOrThrow
@Suppress("unused")
val premiumIconPatch = bytecodePatch(
PREMIUM_ICON.title,
PREMIUM_ICON.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
execute {
premiumIconFingerprint.methodOrThrow().addInstructions(
0, """
const/4 v0, 0x1
return v0
"""
)
updatePatchStatus(PREMIUM_ICON)
}
}

View File

@ -0,0 +1,17 @@
package app.revanced.patches.reddit.layout.recentlyvisited
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
import com.android.tools.smali.dexlib2.Opcode
internal val communityDrawerPresenterFingerprint = legacyFingerprint(
name = "communityDrawerPresenterFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
parameters = emptyList(),
opcodes = listOf(Opcode.AGET),
customFingerprint = { method, _ ->
method.definingClass.endsWith("/CommunityDrawerPresenter;")
}
)

View File

@ -0,0 +1,80 @@
package app.revanced.patches.reddit.layout.recentlyvisited
import app.revanced.patcher.extensions.InstructionExtensions.addInstructions
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.HIDE_RECENTLY_VISITED_SHELF
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.findMethodOrThrow
import app.revanced.util.fingerprint.methodOrThrow
import app.revanced.util.getReference
import app.revanced.util.indexOfFirstInstructionOrThrow
import app.revanced.util.indexOfFirstInstructionReversedOrThrow
import com.android.tools.smali.dexlib2.Opcode
import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction
import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction
import com.android.tools.smali.dexlib2.iface.reference.FieldReference
private const val EXTENSION_METHOD_DESCRIPTOR =
"$PATCHES_PATH/RecentlyVisitedShelfPatch;" +
"->" +
"hideRecentlyVisitedShelf(Ljava/util/List;)Ljava/util/List;"
@Suppress("unused")
val recentlyVisitedShelfPatch = bytecodePatch(
HIDE_RECENTLY_VISITED_SHELF.title,
HIDE_RECENTLY_VISITED_SHELF.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(settingsPatch)
execute {
val communityDrawerPresenterMethod = communityDrawerPresenterFingerprint.methodOrThrow()
val constructorMethod = findMethodOrThrow(communityDrawerPresenterMethod.definingClass)
val recentlyVisitedReference = with(constructorMethod) {
val recentlyVisitedFieldIndex = indexOfFirstInstructionOrThrow {
getReference<FieldReference>()?.name == "RECENTLY_VISITED"
}
val recentlyVisitedObjectIndex =
indexOfFirstInstructionOrThrow(
recentlyVisitedFieldIndex,
Opcode.IPUT_OBJECT
)
getInstruction<ReferenceInstruction>(recentlyVisitedObjectIndex).reference
}
communityDrawerPresenterMethod.apply {
val recentlyVisitedObjectIndex = indexOfFirstInstructionOrThrow {
getReference<FieldReference>()?.toString() == recentlyVisitedReference.toString()
}
arrayOf(
indexOfFirstInstructionOrThrow(
recentlyVisitedObjectIndex,
Opcode.INVOKE_STATIC
),
indexOfFirstInstructionReversedOrThrow(
recentlyVisitedObjectIndex,
Opcode.INVOKE_STATIC
)
).forEach { staticIndex ->
val insertRegister =
getInstruction<OneRegisterInstruction>(staticIndex + 1).registerA
addInstructions(
staticIndex + 2, """
invoke-static {v$insertRegister}, $EXTENSION_METHOD_DESCRIPTOR
move-result-object v$insertRegister
"""
)
}
}
updatePatchStatus(
"enableRecentlyVisitedShelf",
HIDE_RECENTLY_VISITED_SHELF
)
}
}

View File

@ -0,0 +1,16 @@
package app.revanced.patches.reddit.layout.screenshotpopup
import app.revanced.patches.reddit.utils.resourceid.screenShotShareBanner
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val screenshotTakenBannerFingerprint = legacyFingerprint(
name = "screenshotTakenBannerFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(screenShotShareBanner),
customFingerprint = { _, classDef ->
classDef.sourceFile == "ScreenshotTakenBanner.kt"
}
)

View File

@ -0,0 +1,47 @@
package app.revanced.patches.reddit.layout.screenshotpopup
import app.revanced.patcher.extensions.InstructionExtensions.addInstructionsWithLabels
import app.revanced.patcher.extensions.InstructionExtensions.getInstruction
import app.revanced.patcher.patch.bytecodePatch
import app.revanced.patcher.util.smali.ExternalLabel
import app.revanced.patches.reddit.utils.compatibility.Constants.COMPATIBLE_PACKAGE
import app.revanced.patches.reddit.utils.extension.Constants.PATCHES_PATH
import app.revanced.patches.reddit.utils.patch.PatchList.DISABLE_SCREENSHOT_POPUP
import app.revanced.patches.reddit.utils.resourceid.sharedResourceIdPatch
import app.revanced.patches.reddit.utils.settings.settingsPatch
import app.revanced.patches.reddit.utils.settings.updatePatchStatus
import app.revanced.util.fingerprint.methodOrThrow
private const val EXTENSION_METHOD_DESCRIPTOR =
"$PATCHES_PATH/ScreenshotPopupPatch;->disableScreenshotPopup()Z"
@Suppress("unused")
val screenshotPopupPatch = bytecodePatch(
DISABLE_SCREENSHOT_POPUP.title,
DISABLE_SCREENSHOT_POPUP.summary,
) {
compatibleWith(COMPATIBLE_PACKAGE)
dependsOn(
sharedResourceIdPatch,
settingsPatch
)
execute {
screenshotTakenBannerFingerprint.methodOrThrow().apply {
addInstructionsWithLabels(
0, """
invoke-static {}, $EXTENSION_METHOD_DESCRIPTOR
move-result v0
if-eqz v0, :dismiss
return-void
""", ExternalLabel("dismiss", getInstruction(0))
)
}
updatePatchStatus(
"enableScreenshotPopup",
DISABLE_SCREENSHOT_POPUP
)
}
}

View File

@ -0,0 +1,27 @@
package app.revanced.patches.reddit.layout.subredditdialog
import app.revanced.patches.reddit.utils.resourceid.cancelButton
import app.revanced.patches.reddit.utils.resourceid.textAppearanceRedditBaseOldButtonColored
import app.revanced.util.fingerprint.legacyFingerprint
import app.revanced.util.or
import com.android.tools.smali.dexlib2.AccessFlags
internal val frequentUpdatesSheetScreenFingerprint = legacyFingerprint(
name = "frequentUpdatesSheetScreenFingerprint",
returnType = "Landroid/view/View;",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(cancelButton),
customFingerprint = { _, classDef ->
classDef.sourceFile == "FrequentUpdatesSheetScreen.kt"
}
)
internal val redditAlertDialogsFingerprint = legacyFingerprint(
name = "redditAlertDialogsFingerprint",
returnType = "V",
accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL,
literals = listOf(textAppearanceRedditBaseOldButtonColored),
customFingerprint = { _, classDef ->
classDef.sourceFile == "RedditAlertDialogs.kt"
}
)

Some files were not shown because too many files have changed in this diff Show More