diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt index f1fe0d3f1..9f426080b 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/general/downloads/DownloadActionsPatch.kt @@ -176,7 +176,7 @@ object DownloadActionsPatch : BaseBytecodePatch( SettingsPatch.addPreference( arrayOf( "PREFERENCE_SCREEN: GENERAL", - "PREFERENCE_CATEGORY: GENERAL_EXPERIMENTAL_FLAGS", + "SETTINGS: HOOK_BUTTONS", "SETTINGS: HOOK_DOWNLOAD_ACTIONS" ) ) diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt new file mode 100644 index 000000000..acf3b1d00 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/music/YouTubeMusicActionsPatch.kt @@ -0,0 +1,108 @@ +package app.revanced.patches.youtube.general.music + +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.replaceInstruction +import app.revanced.patches.youtube.general.music.fingerprints.AppDeepLinkFingerprint +import app.revanced.patches.youtube.utils.compatibility.Constants.COMPATIBLE_PACKAGE +import app.revanced.patches.youtube.utils.gms.GmsCoreSupportResourcePatch.PackageNameYouTubeMusic +import app.revanced.patches.youtube.utils.integrations.Constants.GENERAL_PATH +import app.revanced.patches.youtube.utils.settings.ResourceUtils.addEntryValues +import app.revanced.patches.youtube.utils.settings.SettingsBytecodePatch +import app.revanced.patches.youtube.utils.settings.SettingsPatch +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstructionOrThrow +import app.revanced.util.patch.BaseBytecodePatch +import app.revanced.util.resultOrThrow +import app.revanced.util.valueOrThrow +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 +import java.io.Closeable + +@Suppress("unused") +object YouTubeMusicActionsPatch : BaseBytecodePatch( + name = "Hook YouTube Music actions", + description = "Adds support for opening music in RVX Music using the in-app YouTube Music button.", + dependencies = setOf(SettingsPatch::class), + compatiblePackages = COMPATIBLE_PACKAGE, + fingerprints = setOf(AppDeepLinkFingerprint) +), Closeable { + private const val INTEGRATIONS_CLASS_DESCRIPTOR = + "$GENERAL_PATH/YouTubeMusicActionsPatch;" + + override fun execute(context: BytecodeContext) { + + AppDeepLinkFingerprint.resultOrThrow().let { + it.mutableMethod.apply { + val packageNameIndex = it.scanResult.patternScanResult!!.startIndex + val packageNameField = getInstruction(packageNameIndex).reference.toString() + + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + instruction.opcode == Opcode.IGET_OBJECT && + instruction.getReference()?.toString() == packageNameField + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = getInstruction(index).registerA + + addInstructions( + index + 1, """ + invoke-static {v$register}, $INTEGRATIONS_CLASS_DESCRIPTOR->overridePackageName(Ljava/lang/String;)Ljava/lang/String; + move-result-object v$register + """ + ) + } + } + } + + /** + * Add settings + */ + SettingsPatch.addPreference( + arrayOf( + "PREFERENCE_SCREEN: GENERAL", + "SETTINGS: HOOK_BUTTONS", + "SETTINGS: HOOK_YOUTUBE_MUSIC_ACTIONS" + ) + ) + + SettingsPatch.updatePatchStatus(this) + } + + override fun close() { + if (SettingsPatch.containsPatch("GmsCore support")) { + SettingsPatch.contexts.addEntryValues( + "revanced_third_party_youtube_music_label", + "RVX Music" + ) + SettingsPatch.contexts.addEntryValues( + "revanced_third_party_youtube_music_package_name", + PackageNameYouTubeMusic.valueOrThrow() + ) + + SettingsBytecodePatch.contexts + .findClass { classDef -> classDef.type == INTEGRATIONS_CLASS_DESCRIPTOR } + ?.mutableClass + ?.methods + ?.first { method -> method.name == "getRVXMusicPackageName" } + ?.apply { + val replaceIndex = indexOfFirstInstructionOrThrow(Opcode.CONST_STRING) + val replaceRegister = + getInstruction(replaceIndex).registerA + + replaceInstruction( + replaceIndex, + "const-string v$replaceRegister, \"${PackageNameYouTubeMusic.valueOrThrow()}\"" + ) + } + } + + } +} \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt b/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt new file mode 100644 index 000000000..d4d7d2330 --- /dev/null +++ b/src/main/kotlin/app/revanced/patches/youtube/general/music/fingerprints/AppDeepLinkFingerprint.kt @@ -0,0 +1,27 @@ +package app.revanced.patches.youtube.general.music.fingerprints + +import app.revanced.patcher.extensions.or +import app.revanced.patcher.fingerprint.MethodFingerprint +import app.revanced.util.getReference +import app.revanced.util.indexOfFirstInstruction +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.Opcode +import com.android.tools.smali.dexlib2.iface.reference.FieldReference + +internal object AppDeepLinkFingerprint : MethodFingerprint( + returnType = "V", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("L", "Ljava/util/Map;"), + opcodes = listOf( + Opcode.IGET_OBJECT, + Opcode.INVOKE_VIRTUAL, + Opcode.MOVE_RESULT, + Opcode.CONST_STRING, + ), + strings = listOf("android.intent.action.VIEW"), + customFingerprint = { methodDef, _ -> + methodDef.indexOfFirstInstruction { + getReference()?.name == "appDeepLinkEndpoint" + } >= 0 + } +) \ No newline at end of file diff --git a/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt index b4c8bb7dc..3d1bc9182 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/layout/visual/VisualPreferencesIconsPatch.kt @@ -318,6 +318,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_preference_screen_feed_flyout_menu", "revanced_preference_screen_fullscreen", "revanced_preference_screen_haptic_feedback", + "revanced_preference_screen_hook_buttons", "revanced_preference_screen_import_export", "revanced_preference_screen_miniplayer", "revanced_preference_screen_navigation_buttons", @@ -430,6 +431,7 @@ object VisualPreferencesIconsPatch : BaseResourcePatch( "revanced_preference_screen_comments" -> "revanced_hide_quick_actions_comment_button_icon" "revanced_preference_screen_feed_flyout_menu" -> "revanced_preference_screen_player_flyout_menu_icon" "revanced_preference_screen_haptic_feedback" -> "revanced_enable_swipe_haptic_feedback_icon" + "revanced_preference_screen_hook_buttons" -> "revanced_preference_screen_import_export_icon" "revanced_preference_screen_miniplayer" -> "offline_key_icon" "revanced_preference_screen_patch_information" -> "about_key_icon" "revanced_preference_screen_shorts_player" -> "revanced_preference_screen_shorts_icon" diff --git a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt index ffde12dd8..385c536a6 100644 --- a/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt +++ b/src/main/kotlin/app/revanced/patches/youtube/utils/settings/SettingsPatch.kt @@ -316,7 +316,13 @@ object SettingsPatch : BaseResourcePatch( updatePatchStatus(patch.name!!) } + private val patchList = ArrayList() + internal fun updatePatchStatus(patchName: String) { + patchList.add(patchName) contexts.updatePatchStatus(patchName) } + + internal fun containsPatch(patchName: String) = + patchList.contains(patchName) } diff --git a/src/main/resources/youtube/settings/host/values/arrays.xml b/src/main/resources/youtube/settings/host/values/arrays.xml index ba6d4cc25..d457148bd 100644 --- a/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/src/main/resources/youtube/settings/host/values/arrays.xml @@ -185,6 +185,12 @@ 17.41.37 17.33.42 + + YouTube Music + + + com.google.android.apps.youtube.music + @string/revanced_watch_history_type_entry_1 @string/revanced_watch_history_type_entry_2 diff --git a/src/main/resources/youtube/settings/host/values/strings.xml b/src/main/resources/youtube/settings/host/values/strings.xml index f107c7ffd..401587c18 100644 --- a/src/main/resources/youtube/settings/host/values/strings.xml +++ b/src/main/resources/youtube/settings/host/values/strings.xml @@ -351,15 +351,6 @@ This does not bypass the age restriction. It just accepts it automatically."Enable tablet layout Spoofs the dpi to use some tablet layouts. - Override video download button - Native video download button opens your external downloader. - Native video download button opens the native in-app downloader. - Override playlist download button - Native playlist download button is always shown, and in public playlists, it opens your external downloader. - If shown, the native playlist download button opens the native in-app downloader. - Playlist downloader package name - Package name of your installed external downloader app, such as YTDLnis. - Spoof app version Version spoofed Version not spoofed @@ -404,6 +395,34 @@ Some components may not be hidden." List of component path builder strings to filter, separated by new lines. Invalid custom filter: %s. + + Hook buttons + Overrides the click action of in-app buttons. + + + Download button + + Override video download button + Native video download button opens your external downloader. + Native video download button opens the native in-app downloader. + Override playlist download button + Native playlist download button is always shown, and in public playlists, it opens your external downloader. + If shown, the native playlist download button opens the native in-app downloader. + Playlist downloader package name + Package name of your installed external downloader app, such as YTDLnis. + + + Override YouTube Music button + YouTube Music button opens the RVX Music. + YouTube Music button opens the native app. + RVX Music package name + Package name of installed RVX Music. + RVX Music + Warning + %s is not installed. Please install it. + Prerequisite + YouTube Music is required to override button action. Tap here to download YouTube Music. + Miniplayer Change the style of the in app minimized player. diff --git a/src/main/resources/youtube/settings/xml/revanced_prefs.xml b/src/main/resources/youtube/settings/xml/revanced_prefs.xml index 309b26009..b149a2c98 100644 --- a/src/main/resources/youtube/settings/xml/revanced_prefs.xml +++ b/src/main/resources/youtube/settings/xml/revanced_prefs.xml @@ -132,6 +132,27 @@ SETTINGS: HIDE_LAYOUT_COMPONENTS --> + + + + + + + + @@ -230,12 +251,6 @@ SETTINGS: LAYOUT_SWITCH --> - -