From f97d6de1c57b1d19ab9b6164e1043f37e66b2903 Mon Sep 17 00:00:00 2001 From: inotia00 Date: Tue, 5 Sep 2023 12:15:25 +0900 Subject: [PATCH] feat(music): add `return-youtube-dislike` patch --- .../resourceid/patch/SharedResourceIdPatch.kt | 2 + .../fingerprints/DislikeFingerprint.kt | 8 ++ .../bytecode/fingerprints/LikeFingerprint.kt | 8 ++ .../fingerprints/RemoveLikeFingerprint.kt | 8 ++ .../fingerprints/TextComponentFingerprint.kt | 12 +++ .../ReturnYouTubeDislikeBytecodePatch.kt | 98 +++++++++++++++++++ .../patch/ReturnYouTubeDislikePatch.kt | 60 ++++++++++++ .../util/resources/MusicResourceHelper.kt | 42 ++++++++ .../xml/returnyoutubedislike_prefs.xml | 12 +++ .../music/settings/host/values/strings.xml | 10 ++ .../music/settings/values-v21/strings.xml | 2 + 11 files changed, 262 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/DislikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/LikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/RemoveLikeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/TextComponentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/patch/ReturnYouTubeDislikeBytecodePatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/resource/patch/ReturnYouTubeDislikePatch.kt create mode 100644 src/main/resources/music/returnyoutubedislike/xml/returnyoutubedislike_prefs.xml diff --git a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt index 410cb21c1..c6e84ac4a 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/resourceid/patch/SharedResourceIdPatch.kt @@ -18,6 +18,7 @@ import app.revanced.util.enum.ResourceType.STYLE class SharedResourceIdPatch : ResourcePatch { internal companion object { var ActionsContainer: Long = -1 + var ButtonIconPaddingMedium: Long = -1 var ChipCloud: Long = -1 var ColorGrey: Long = -1 var DialogSolid: Long = -1 @@ -39,6 +40,7 @@ class SharedResourceIdPatch : ResourcePatch { ?: throw PatchException("Failed to find resource id : $resourceName") ActionsContainer = find(ID, "actions_container") + ButtonIconPaddingMedium = find(DIMEN, "button_icon_padding_medium") ChipCloud = find(LAYOUT, "chip_cloud") ColorGrey = find(COLOR, "ytm_color_grey_12") DialogSolid = find(STYLE, "Theme.YouTubeMusic.Dialog.Solid") diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/DislikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/DislikeFingerprint.kt new file mode 100644 index 000000000..40c89157d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/DislikeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object DislikeFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("like/dislike") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/LikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/LikeFingerprint.kt new file mode 100644 index 000000000..f0cf504d6 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/LikeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object LikeFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("like/like") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/RemoveLikeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/RemoveLikeFingerprint.kt new file mode 100644 index 000000000..3d841ffb2 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/RemoveLikeFingerprint.kt @@ -0,0 +1,8 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint + +object RemoveLikeFingerprint : MethodFingerprint( + returnType = "V", + strings = listOf("like/removelike") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/TextComponentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/TextComponentFingerprint.kt new file mode 100644 index 000000000..0512bbe5d --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/fingerprints/TextComponentFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch.Companion.ButtonIconPaddingMedium +import app.revanced.util.bytecode.isWideLiteralExists +import com.android.tools.smali.dexlib2.Opcode + +object TextComponentFingerprint : MethodFingerprint( + returnType = "V", + opcodes = listOf(Opcode.CONST_HIGH16), + customFingerprint = { methodDef, _ -> methodDef.isWideLiteralExists(ButtonIconPaddingMedium) } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/patch/ReturnYouTubeDislikeBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/patch/ReturnYouTubeDislikeBytecodePatch.kt new file mode 100644 index 000000000..084ebbfe4 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/bytecode/patch/ReturnYouTubeDislikeBytecodePatch.kt @@ -0,0 +1,98 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.bytecode.patch + +import app.revanced.extensions.exception +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstructions +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.PatchException +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch +import app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints.DislikeFingerprint +import app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints.LikeFingerprint +import app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints.RemoveLikeFingerprint +import app.revanced.patches.music.utils.returnyoutubedislike.bytecode.fingerprints.TextComponentFingerprint +import app.revanced.patches.music.utils.videoid.patch.VideoIdPatch +import app.revanced.util.integrations.Constants.MUSIC_UTILS_PATH +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction +import com.android.tools.smali.dexlib2.iface.instruction.formats.Instruction35c + +@DependsOn( + [ + SharedResourceIdPatch::class, + VideoIdPatch::class + ] +) +class ReturnYouTubeDislikeBytecodePatch : BytecodePatch( + listOf( + DislikeFingerprint, + LikeFingerprint, + RemoveLikeFingerprint, + TextComponentFingerprint + ) +) { + override fun execute(context: BytecodeContext) { + listOf( + LikeFingerprint.toPatch(Vote.LIKE), + DislikeFingerprint.toPatch(Vote.DISLIKE), + RemoveLikeFingerprint.toPatch(Vote.REMOVE_LIKE) + ).forEach { (fingerprint, vote) -> + with(fingerprint.result ?: throw fingerprint.exception) { + mutableMethod.addInstructions( + 0, + """ + const/4 v0, ${vote.value} + invoke-static {v0}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->sendVote(I)V + """ + ) + } + } + + TextComponentFingerprint.result?.let { + it.mutableMethod.apply { + var insertIndex = -1 + for ((index, instruction) in implementation!!.instructions.withIndex()) { + if (instruction.opcode != Opcode.INVOKE_STATIC) continue + + val reference = getInstruction(index).reference.toString() + if (!reference.endsWith("Ljava/lang/CharSequence;") && !reference.endsWith("Landroid/text/Spanned;")) continue + + val insertRegister = getInstruction(index + 1).registerA + + insertIndex = index + 2 + + addInstructions( + insertIndex, """ + invoke-static {v$insertRegister}, $INTEGRATIONS_RYD_CLASS_DESCRIPTOR->onComponentCreated(Landroid/text/Spanned;)Landroid/text/Spanned; + move-result-object v$insertRegister + """ + ) + + break + } + if (insertIndex == -1) + throw PatchException("target Instruction not found!") + } + } ?: throw TextComponentFingerprint.exception + + VideoIdPatch.injectCall("$INTEGRATIONS_RYD_CLASS_DESCRIPTOR->newVideoLoaded(Ljava/lang/String;)V") + + } + + private companion object { + const val INTEGRATIONS_RYD_CLASS_DESCRIPTOR = + "$MUSIC_UTILS_PATH/ReturnYouTubeDislikePatch;" + } + + private fun MethodFingerprint.toPatch(voteKind: Vote) = VotePatch(this, voteKind) + + private data class VotePatch(val fingerprint: MethodFingerprint, val voteKind: Vote) + + private enum class Vote(val value: Int) { + LIKE(1), + DISLIKE(-1), + REMOVE_LIKE(0) + } +} diff --git a/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/resource/patch/ReturnYouTubeDislikePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/resource/patch/ReturnYouTubeDislikePatch.kt new file mode 100644 index 000000000..b6b2dbe2f --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/returnyoutubedislike/resource/patch/ReturnYouTubeDislikePatch.kt @@ -0,0 +1,60 @@ +package app.revanced.patches.music.utils.returnyoutubedislike.resource.patch + +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.data.ResourceContext +import app.revanced.patcher.patch.ResourcePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.utils.annotations.MusicCompatibility +import app.revanced.patches.music.utils.returnyoutubedislike.bytecode.patch.ReturnYouTubeDislikeBytecodePatch +import app.revanced.patches.music.utils.settings.resource.patch.SettingsPatch +import app.revanced.util.resources.MusicResourceHelper.hookPreference +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources + +@Patch +@Name("Return YouTube Dislike") +@Description("Shows the dislike count of videos using the Return YouTube Dislike API.") +@DependsOn( + [ + ReturnYouTubeDislikeBytecodePatch::class, + SettingsPatch::class + ] +) +@MusicCompatibility +class ReturnYouTubeDislikePatch : ResourcePatch { + override fun execute(context: ResourceContext) { + + /** + * Copy preference + */ + arrayOf( + ResourceUtils.ResourceGroup( + "xml", + "returnyoutubedislike_prefs.xml" + ) + ).forEach { resourceGroup -> + context.copyResources("music/returnyoutubedislike", resourceGroup) + } + + /** + * Hook RYD preference + */ + context.hookPreference( + "revanced_ryd_settings", + "com.google.android.apps.youtube.music.settings.fragment.AdvancedPrefsFragmentCompat" + ) + + val publicFile = context["res/values/public.xml"] + + publicFile.writeText( + publicFile.readText() + .replace( + "\"advanced_prefs_compat\"", + "\"returnyoutubedislike_prefs\"" + ) + ) + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/util/resources/MusicResourceHelper.kt b/src/main/kotlin/app/revanced/util/resources/MusicResourceHelper.kt index 5c3dbef93..04de4bbf4 100644 --- a/src/main/kotlin/app/revanced/util/resources/MusicResourceHelper.kt +++ b/src/main/kotlin/app/revanced/util/resources/MusicResourceHelper.kt @@ -212,4 +212,46 @@ internal object MusicResourceHelper { } } + internal fun ResourceContext.hookPreference( + key: String, + fragment: String + ) { + this.xmlEditor[YOUTUBE_MUSIC_SETTINGS_PATH].use { editor -> + with(editor.file) { + doRecursively loop@{ + if (it !is Element) return@loop + it.getAttributeNode("android:key")?.let { attribute -> + if (attribute.textContent == "settings_header_about_youtube_music" && it.getAttributeNode( + "app:allowDividerBelow" + ).textContent == "false" + ) { + it.insertNode("Preference", it) { + setAttribute("android:persistent", "false") + setAttribute( + "android:title", + "@string/" + key + "_title" + ) + setAttribute("android:key", key) + setAttribute("android:fragment", fragment) + setAttribute("app:allowDividerAbove", "false") + setAttribute("app:allowDividerAbove", "false") + } + it.getAttributeNode("app:allowDividerBelow").textContent = "true" + return@loop + } + } + } + + doRecursively loop@{ + if (it !is Element) return@loop + + it.getAttributeNode("app:allowDividerBelow")?.let { attribute -> + if (attribute.textContent == "true") { + attribute.textContent = "false" + } + } + } + } + } + } } \ No newline at end of file diff --git a/src/main/resources/music/returnyoutubedislike/xml/returnyoutubedislike_prefs.xml b/src/main/resources/music/returnyoutubedislike/xml/returnyoutubedislike_prefs.xml new file mode 100644 index 000000000..337246087 --- /dev/null +++ b/src/main/resources/music/returnyoutubedislike/xml/returnyoutubedislike_prefs.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/music/settings/host/values/strings.xml b/src/main/resources/music/settings/host/values/strings.xml index 64fe19342..4b4336ff4 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -93,6 +93,16 @@ Hook download button Refresh and restart Reset + About + Data is provided by the Return YouTube Dislike API. Tap here to learn more. + ReturnYouTubeDislike.com + Hides the separator of the like button. + Compact like button + Instead of the number of dislikes, the percentage of dislikes is shown. + Dislikes as percentage + Shows the dislike count of videos. + Dislikes not available (client API limit reached) + Hidden Changing default speed to: Changing default mobile data quality to: Failed to set quality diff --git a/src/main/resources/music/settings/values-v21/strings.xml b/src/main/resources/music/settings/values-v21/strings.xml index 30340fbe4..495a4e6ab 100644 --- a/src/main/resources/music/settings/values-v21/strings.xml +++ b/src/main/resources/music/settings/values-v21/strings.xml @@ -2,4 +2,6 @@ ReVanced Extended + @string/revanced_ryd_settings_title + Return YouTube Dislike