From 08d236bfb4b4d1dfceef276442d9f9fa330559e7 Mon Sep 17 00:00:00 2001 From: inotia00 <108592928+inotia00@users.noreply.github.com> Date: Fri, 29 Sep 2023 21:17:27 +0900 Subject: [PATCH] feat(music): add `repace-cast-button` patch --- .../CastButtonContainerFingerprint.kt | 13 ++ .../PlaybackStartDescriptorFingerprint.kt | 31 +++++ .../replace/patch/ReplaceCastButtonPatch.kt | 121 ++++++++++++++++++ .../resourceid/patch/SharedResourceIdPatch.kt | 2 + .../bytecode/patch/SettingsBytecodePatch.kt | 1 + .../fingerprint/VideoTypeFingerprint.kt | 22 ++++ .../fingerprint/VideoTypeParentFingerprint.kt | 12 ++ .../videotype/patch/VideoTypeHookPatch.kt | 47 +++++++ .../music/cast/layout/open_music_button.xml | 5 + .../music/settings/host/values/strings.xml | 4 + 10 files changed, 258 insertions(+) create mode 100644 src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/CastButtonContainerFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/PlaybackStartDescriptorFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/player/replace/patch/ReplaceCastButtonPatch.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeParentFingerprint.kt create mode 100644 src/main/kotlin/app/revanced/patches/music/utils/videotype/patch/VideoTypeHookPatch.kt create mode 100644 src/main/resources/music/cast/layout/open_music_button.xml diff --git a/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/CastButtonContainerFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/CastButtonContainerFingerprint.kt new file mode 100644 index 000000000..4671e985b --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/CastButtonContainerFingerprint.kt @@ -0,0 +1,13 @@ +package app.revanced.patches.music.player.replace.fingerprints + +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch.Companion.PlayerCastMediaRouteButton +import app.revanced.util.bytecode.isWideLiteralExists + +object CastButtonContainerFingerprint : MethodFingerprint( + returnType = "V", + customFingerprint = { methodDef, _ -> + methodDef.isWideLiteralExists(PlayerCastMediaRouteButton) + && methodDef.definingClass.endsWith("/MusicActivity;") + } +) diff --git a/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/PlaybackStartDescriptorFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/PlaybackStartDescriptorFingerprint.kt new file mode 100644 index 000000000..ed4ec4b89 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/replace/fingerprints/PlaybackStartDescriptorFingerprint.kt @@ -0,0 +1,31 @@ +package app.revanced.patches.music.player.replace.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object PlaybackStartDescriptorFingerprint : MethodFingerprint( + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + returnType = "L", + parameters = listOf( + "Ljava/lang/String;", + "[B", + "Ljava/lang/String;", + "Ljava/lang/String;", + "I", + "I", + "Ljava/util/Set;", + "Ljava/lang/String;", + "Ljava/lang/String;", + "L", + "Z", + "Z" + ), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT, + Opcode.CHECK_CAST, + Opcode.INVOKE_INTERFACE + ) +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/player/replace/patch/ReplaceCastButtonPatch.kt b/src/main/kotlin/app/revanced/patches/music/player/replace/patch/ReplaceCastButtonPatch.kt new file mode 100644 index 000000000..388e0abc3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/player/replace/patch/ReplaceCastButtonPatch.kt @@ -0,0 +1,121 @@ +package app.revanced.patches.music.player.replace.patch + +import app.revanced.extensions.exception +import app.revanced.patcher.annotation.Description +import app.revanced.patcher.annotation.Name +import app.revanced.patcher.data.BytecodeContext +import app.revanced.patcher.extensions.InstructionExtensions.addInstruction +import app.revanced.patcher.extensions.InstructionExtensions.getInstruction +import app.revanced.patcher.extensions.InstructionExtensions.removeInstruction +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patcher.patch.annotations.DependsOn +import app.revanced.patcher.patch.annotations.Patch +import app.revanced.patches.music.player.replace.fingerprints.CastButtonContainerFingerprint +import app.revanced.patches.music.player.replace.fingerprints.PlaybackStartDescriptorFingerprint +import app.revanced.patches.music.utils.annotations.MusicCompatibility +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch +import app.revanced.patches.music.utils.resourceid.patch.SharedResourceIdPatch.Companion.PlayerCastMediaRouteButton +import app.revanced.patches.music.utils.settings.resource.patch.SettingsPatch +import app.revanced.patches.music.utils.settings.resource.patch.SettingsPatch.Companion.contexts +import app.revanced.patches.music.utils.videotype.patch.VideoTypeHookPatch +import app.revanced.util.bytecode.getWideLiteralIndex +import app.revanced.util.enum.CategoryType +import app.revanced.util.integrations.Constants.MUSIC_PLAYER +import app.revanced.util.integrations.Constants.MUSIC_UTILS_PATH +import app.revanced.util.resources.ResourceUtils +import app.revanced.util.resources.ResourceUtils.copyResources +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.instruction.formats.Instruction35c +import com.android.tools.smali.dexlib2.iface.reference.MethodReference + +@Patch(false) +@Name("Replace cast button") +@Description("Replace the cast button in the player with the open music button.") +@DependsOn( + [ + SettingsPatch::class, + SharedResourceIdPatch::class, + VideoTypeHookPatch::class + ] +) +@MusicCompatibility +class ReplaceCastButtonPatch : BytecodePatch( + listOf( + CastButtonContainerFingerprint, + PlaybackStartDescriptorFingerprint + ) +) { + override fun execute(context: BytecodeContext) { + + CastButtonContainerFingerprint.result?.let { + it.mutableMethod.apply { + val freeIndex = getWideLiteralIndex(PlayerCastMediaRouteButton) + 1 + val freeRegister = getInstruction(freeIndex).registerA + + val getActivityIndex = freeIndex - 4 + val getActivityRegister = getInstruction(getActivityIndex).registerB + val getActivityReference = getInstruction(getActivityIndex).reference + + for (index in freeIndex + 20 downTo freeIndex) { + if (getInstruction(index).opcode != Opcode.INVOKE_VIRTUAL) + continue + + if ((getInstruction(index).reference as MethodReference).name != "addView") + continue + + val viewGroupInstruction = getInstruction(index) + + addInstruction( + index + 1, + "invoke-static {v$freeRegister, v${viewGroupInstruction.registerC}, v${viewGroupInstruction.registerD}}, " + + MUSIC_PLAYER + + "->" + + "replaceCastButton(Landroid/app/Activity;Landroid/view/ViewGroup;Landroid/view/View;)V" + ) + addInstruction( + index + 1, + "iget-object v$freeRegister, v$getActivityRegister, $getActivityReference" + ) + removeInstruction(index) + + break + } + } + } ?: throw CastButtonContainerFingerprint.exception + + PlaybackStartDescriptorFingerprint.result?.let { + it.mutableMethod.apply { + val videoIdRegister = 1 + val playlistIdRegister = 4 + val playlistIndexRegister = 5 + + addInstruction( + 0, + "invoke-static {p$videoIdRegister, p$playlistIdRegister, p$playlistIndexRegister}, " + + "$MUSIC_UTILS_PATH/CheckMusicVideoPatch;" + + "->" + + "playbackStart(Ljava/lang/String;Ljava/lang/String;I)V" + ) + } + } ?: throw PlaybackStartDescriptorFingerprint.exception + + arrayOf( + ResourceUtils.ResourceGroup( + "layout", + "open_music_button.xml" + ) + ).forEach { resourceGroup -> + contexts.copyResources("music/cast", resourceGroup) + } + + SettingsPatch.addMusicPreference( + CategoryType.PLAYER, + "revanced_replace_player_cast_button", + "false" + ) + + } +} 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 ac850932c..135147f98 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 @@ -27,6 +27,7 @@ class SharedResourceIdPatch : ResourcePatch { var MenuEntry: Long = -1 var MusicMenuLikeButtons: Long = -1 var NamesInactiveAccountThumbnailSize: Long = -1 + var PlayerCastMediaRouteButton: Long = -1 var PlayerOverlayChip: Long = -1 var PrivacyTosFooter: Long = -1 var QualityAuto: Long = -1 @@ -53,6 +54,7 @@ class SharedResourceIdPatch : ResourcePatch { MenuEntry = find(LAYOUT, "menu_entry") MusicMenuLikeButtons = find(LAYOUT, "music_menu_like_buttons") NamesInactiveAccountThumbnailSize = find(DIMEN, "names_inactive_account_thumbnail_size") + PlayerCastMediaRouteButton = find(LAYOUT, "player_cast_media_route_button") PlayerOverlayChip = find(ID, "player_overlay_chip") PrivacyTosFooter = find(ID, "privacy_tos_footer") QualityAuto = find(STRING, "quality_auto") diff --git a/src/main/kotlin/app/revanced/patches/music/utils/settings/bytecode/patch/SettingsBytecodePatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/settings/bytecode/patch/SettingsBytecodePatch.kt index b6d62cc61..3bdac2435 100644 --- a/src/main/kotlin/app/revanced/patches/music/utils/settings/bytecode/patch/SettingsBytecodePatch.kt +++ b/src/main/kotlin/app/revanced/patches/music/utils/settings/bytecode/patch/SettingsBytecodePatch.kt @@ -48,6 +48,7 @@ class SettingsBytecodePatch : BytecodePatch( } } ?: throw PreferenceFingerprint.exception + context.injectInit("InitializationPatch", "setDeviceInformation", false) context.injectInit("InitializationPatch", "initializeReVancedSettings", false) } diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeFingerprint.kt new file mode 100644 index 000000000..453e36474 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeFingerprint.kt @@ -0,0 +1,22 @@ +package app.revanced.patches.music.utils.videotype.fingerprint + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode + +object VideoTypeFingerprint : MethodFingerprint( + 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, + Opcode.RETURN_OBJECT + ) +) diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeParentFingerprint.kt b/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeParentFingerprint.kt new file mode 100644 index 000000000..293e52462 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videotype/fingerprint/VideoTypeParentFingerprint.kt @@ -0,0 +1,12 @@ +package app.revanced.patches.music.utils.videotype.fingerprint + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint +import com.android.tools.smali.dexlib2.AccessFlags + +object VideoTypeParentFingerprint : MethodFingerprint( + returnType = "Z", + accessFlags = AccessFlags.PUBLIC or AccessFlags.STATIC, + parameters = listOf("L", "L"), + strings = listOf("RQ") +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/music/utils/videotype/patch/VideoTypeHookPatch.kt b/src/main/kotlin/app/revanced/patches/music/utils/videotype/patch/VideoTypeHookPatch.kt new file mode 100644 index 000000000..b6901e8e3 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/music/utils/videotype/patch/VideoTypeHookPatch.kt @@ -0,0 +1,47 @@ +package app.revanced.patches.music.utils.videotype.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.extensions.InstructionExtensions.removeInstruction +import app.revanced.patcher.fingerprint.method.impl.MethodFingerprint.Companion.resolve +import app.revanced.patcher.patch.BytecodePatch +import app.revanced.patches.music.utils.videotype.fingerprint.VideoTypeFingerprint +import app.revanced.patches.music.utils.videotype.fingerprint.VideoTypeParentFingerprint +import app.revanced.util.integrations.Constants.MUSIC_UTILS_PATH +import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction + +class VideoTypeHookPatch : BytecodePatch( + listOf(VideoTypeParentFingerprint) +) { + override fun execute(context: BytecodeContext) { + + VideoTypeParentFingerprint.result?.let { parentResult -> + VideoTypeFingerprint.also { + it.resolve( + context, + parentResult.classDef + ) + }.result?.let { + it.mutableMethod.apply { + val videoTypeIndex = it.scanResult.patternScanResult!!.endIndex + val videoTypeRegister = getInstruction(videoTypeIndex).registerA + + addInstructions( + videoTypeIndex + 1, """ + invoke-static {v$videoTypeRegister}, $INTEGRATIONS_CLASS_DESCRIPTOR->setVideoType(Ljava/lang/Enum;)V + return-object v$videoTypeRegister + """ + ) + removeInstruction(videoTypeIndex) + } + } ?: throw VideoTypeFingerprint.exception + } ?: throw VideoTypeParentFingerprint.exception + } + + companion object { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$MUSIC_UTILS_PATH/VideoTypeHookPatch;" + } +} diff --git a/src/main/resources/music/cast/layout/open_music_button.xml b/src/main/resources/music/cast/layout/open_music_button.xml new file mode 100644 index 000000000..60d57d44e --- /dev/null +++ b/src/main/resources/music/cast/layout/open_music_button.xml @@ -0,0 +1,5 @@ + + + + \ 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 a7734d4bb..3adbef19b 100644 --- a/src/main/resources/music/settings/host/values/strings.xml +++ b/src/main/resources/music/settings/host/values/strings.xml @@ -125,12 +125,16 @@ Hide upgrade button Replaces the offline download button with an external download button. Hook download button + Already playing official music source + Official music source not available Restart to load the layout normally Refresh and restart When watching on YouTube, continue watching from the current time. Continue watching Replaces dismiss queue menu to watch on YouTube. Replace dismiss queue + Replace the cast button in the player with the open music button. (Experimental) + Replace cast button Remembers the state of the repeat. Remember repeat state Remembers the state of the shuffle.