mirror of
https://github.com/inotia00/revanced-patches.git
synced 2025-06-12 05:07:41 +02:00
refactor: Bump ReVanced Patcher & merge integrations by using ReVanced Patches Gradle plugin
BREAKING CHANGE: ReVanced Patcher >= 21 required
This commit is contained in:
@ -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,
|
||||
)
|
||||
}
|
||||
|
||||
}
|
20
patches/src/main/kotlin/app/revanced/generator/Main.kt
Normal file
20
patches/src/main/kotlin/app/revanced/generator/Main.kt
Normal 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) }
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
package app.revanced.generator
|
||||
|
||||
import app.revanced.patcher.patch.Patch
|
||||
|
||||
internal interface PatchesFileGenerator {
|
||||
fun generate(patches: Set<Patch<*>>)
|
||||
}
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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")
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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")
|
||||
},
|
||||
)
|
@ -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)
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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))
|
||||
)
|
||||
}
|
@ -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"
|
||||
}
|
||||
)
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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")
|
||||
)
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
)
|
@ -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)
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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.
|
||||
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
)
|
File diff suppressed because it is too large
Load Diff
@ -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")
|
||||
)
|
@ -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.
|
||||
)
|
||||
)
|
||||
}
|
@ -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;"
|
||||
}
|
@ -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)
|
@ -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
|
||||
}
|
||||
}
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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")
|
||||
)
|
@ -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))
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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"
|
||||
)
|
||||
)
|
@ -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)
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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()
|
||||
),
|
||||
)
|
||||
}
|
@ -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;")
|
||||
}
|
||||
)
|
@ -0,0 +1,5 @@
|
||||
package app.revanced.patches.music.utils.mainactivity
|
||||
|
||||
import app.revanced.patches.shared.mainactivity.baseMainActivityResolvePatch
|
||||
|
||||
val mainActivityResolvePatch = baseMainActivityResolvePatch(mainActivityFingerprint)
|
@ -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."
|
||||
)
|
||||
}
|
@ -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;")
|
||||
}
|
||||
)
|
@ -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"
|
||||
)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
@ -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"
|
||||
]
|
||||
}
|
||||
}
|
@ -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),
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
@ -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"
|
||||
}
|
||||
)
|
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
@ -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" }
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
)
|
@ -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
|
||||
"""
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
)
|
@ -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"
|
||||
)
|
@ -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")
|
||||
)
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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" },
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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"
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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;")
|
||||
}
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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\$")
|
||||
}
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
)
|
@ -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)
|
||||
}
|
||||
}
|
@ -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;")
|
||||
}
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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"
|
||||
}
|
||||
)
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
@ -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
Reference in New Issue
Block a user