diff --git a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java index 50933c1f7..f8fa0890b 100644 --- a/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java +++ b/extensions/shared/src/main/java/app/revanced/extension/youtube/patches/shorts/ShortsRepeatStatePatch.java @@ -2,6 +2,8 @@ package app.revanced.extension.youtube.patches.shorts; import android.app.Activity; +import androidx.annotation.Nullable; + import java.lang.ref.WeakReference; import java.util.Objects; @@ -28,11 +30,16 @@ public class ShortsRepeatStatePatch { END_SCREEN; static void setYTEnumValue(Enum ytBehavior) { + String ytName = ytBehavior.name(); for (ShortsLoopBehavior rvBehavior : values()) { - if (ytBehavior.name().endsWith(rvBehavior.name())) { - rvBehavior.ytEnumValue = ytBehavior; - - Logger.printDebug(() -> rvBehavior + " set to YT enum: " + ytBehavior.name()); + if (ytName.endsWith(rvBehavior.name())) { + if (rvBehavior.ytEnumValue != null) { + Logger.printException(() -> "Conflicting behavior names: " + rvBehavior + + " ytBehavior: " + ytName); + } else { + rvBehavior.ytEnumValue = ytBehavior; + Logger.printDebug(() -> rvBehavior + " set to YT enum: " + ytName); + } return; } } @@ -77,25 +84,39 @@ public class ShortsRepeatStatePatch { /** * Injection point. */ - public static Enum changeShortsRepeatBehavior(Enum original) { + @Nullable + public static Enum changeShortsRepeatBehavior(@Nullable Enum original) { try { - final ShortsLoopBehavior behavior = ExtendedUtils.IS_19_34_OR_GREATER && + if (original == null) { + Logger.printDebug(() -> "Original is null, returning null"); + return null; + } + + ShortsLoopBehavior behavior = ExtendedUtils.IS_19_34_OR_GREATER && isAppInBackgroundPiPMode() ? Settings.CHANGE_SHORTS_BACKGROUND_REPEAT_STATE.get() : Settings.CHANGE_SHORTS_REPEAT_STATE.get(); + Enum overrideBehavior = behavior.ytEnumValue; - if (behavior != ShortsLoopBehavior.UNKNOWN && behavior.ytEnumValue != null) { - Logger.printDebug(() -> behavior.ytEnumValue == original - ? "Changing Shorts repeat behavior from: " + original.name() + " to: " + behavior.ytEnumValue - : "Behavior setting is same as original. Using original: " + original.name() + if (overrideBehavior != null) { + Logger.printDebug(() -> overrideBehavior == original + ? "Behavior setting is same as original. Using original: " + original.name() + : "Changing Shorts repeat behavior from: " + original.name() + " to: " + overrideBehavior.name() ); - return behavior.ytEnumValue; + return overrideBehavior; } } catch (Exception ex) { - Logger.printException(() -> "changeShortsRepeatState failure", ex); + Logger.printException(() -> "changeShortsRepeatBehavior failure", ex); } return original; } + + /** + * Injection point. + */ + public static boolean isAutoPlay(@Nullable Enum original) { + return original != null && ShortsLoopBehavior.SINGLE_PLAY.ytEnumValue == original; + } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt index 00f4cc521..633d8c019 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/Fingerprints.kt @@ -20,6 +20,7 @@ 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.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference import kotlin.collections.listOf @@ -71,6 +72,41 @@ internal val reelEnumStaticFingerprint = legacyFingerprint( returnType = "L" ) +/** + * YouTube 18.49.36 ~ + */ +internal val reelPlaybackRepeatFingerprint = legacyFingerprint( + name = "reelPlaybackRepeatFingerprint", + returnType = "V", + parameters = listOf("L"), + strings = listOf("YoutubePlayerState is in throwing an Error.") +) + +internal val reelPlaybackFingerprint = legacyFingerprint( + name = "reelPlaybackFingerprint", + accessFlags = AccessFlags.PUBLIC or AccessFlags.FINAL, + parameters = listOf("J"), + returnType = "V", + customFingerprint = { method, _ -> + indexOfMilliSecondsInstruction(method) >= 0 && + indexOfInitializationInstruction(method) >= 0 + } +) + +private fun indexOfMilliSecondsInstruction(method: Method) = + method.indexOfFirstInstruction { + getReference()?.name == "MILLISECONDS" + } + +internal fun indexOfInitializationInstruction(method: Method) = + method.indexOfFirstInstruction { + val reference = getReference() + opcode == Opcode.INVOKE_DIRECT && + reference?.name == "" && + reference.parameterTypes.size == 3 && + reference.parameterTypes.firstOrNull() == "I" + } + internal const val SHORTS_HUD_FEATURE_FLAG = 45644023L /** diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt index e327d3e75..cc33f254b 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shorts/components/ShortsComponentPatch.kt @@ -33,10 +33,12 @@ import app.revanced.patches.youtube.utils.patch.PatchList.SHORTS_COMPONENTS import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.playservice.is_18_31_or_greater import app.revanced.patches.youtube.utils.playservice.is_18_34_or_greater +import app.revanced.patches.youtube.utils.playservice.is_18_49_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_02_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_25_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_28_or_greater import app.revanced.patches.youtube.utils.playservice.is_19_34_or_greater +import app.revanced.patches.youtube.utils.playservice.is_20_09_or_greater import app.revanced.patches.youtube.utils.playservice.versionCheckPatch import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverHook import app.revanced.patches.youtube.utils.recyclerview.recyclerViewTreeObserverPatch @@ -60,6 +62,7 @@ import app.revanced.patches.youtube.utils.settings.ResourceUtils.getContext import app.revanced.patches.youtube.utils.settings.settingsPatch import app.revanced.patches.youtube.utils.toolbar.hookToolBar import app.revanced.patches.youtube.utils.toolbar.toolBarHookPatch +import app.revanced.patches.youtube.utils.videoIdFingerprintShorts import app.revanced.patches.youtube.video.information.hookShortsVideoInformation import app.revanced.patches.youtube.video.information.videoInformationPatch import app.revanced.patches.youtube.video.playbackstart.PLAYBACK_START_DESCRIPTOR_CLASS_DESCRIPTOR @@ -71,11 +74,11 @@ import app.revanced.patches.youtube.video.videoid.hookPlayerResponseVideoId import app.revanced.patches.youtube.video.videoid.videoIdPatch import app.revanced.util.REGISTER_TEMPLATE_REPLACEMENT import app.revanced.util.ResourceGroup +import app.revanced.util.addEntryValues import app.revanced.util.cloneMutable import app.revanced.util.copyResources import app.revanced.util.findMethodOrThrow import app.revanced.util.findMutableMethodOf -import app.revanced.util.fingerprint.definingClassOrThrow import app.revanced.util.fingerprint.injectLiteralInstructionBooleanCall import app.revanced.util.fingerprint.matchOrThrow import app.revanced.util.fingerprint.methodOrThrow @@ -94,6 +97,7 @@ import app.revanced.util.or import app.revanced.util.replaceLiteralInstructionCall 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.FiveRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.OneRegisterInstruction import com.android.tools.smali.dexlib2.iface.instruction.ReferenceInstruction @@ -403,9 +407,7 @@ private val shortsRepeatPatch = bytecodePatch( "setMainActivity" ) - val reelEnumClass = reelEnumConstructorFingerprint.definingClassOrThrow() - - reelEnumConstructorFingerprint.methodOrThrow().apply { + val endScreenReference = with (reelEnumConstructorFingerprint.methodOrThrow()) { val insertIndex = indexOfFirstInstructionOrThrow(Opcode.RETURN_VOID) addInstructions( @@ -413,7 +415,7 @@ private val shortsRepeatPatch = bytecodePatch( """ # Pass the first enum value to extension. # Any enum value of this type will work. - sget-object v0, $reelEnumClass->a:$reelEnumClass + sget-object v0, $definingClass->a:$definingClass invoke-static { v0 }, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->setYTShortsRepeatEnum(Ljava/lang/Enum;)V """, ) @@ -422,50 +424,132 @@ private val shortsRepeatPatch = bytecodePatch( indexOfFirstStringInstructionOrThrow("REEL_LOOP_BEHAVIOR_END_SCREEN") val endScreenReferenceIndex = indexOfFirstInstructionOrThrow(endScreenStringIndex, Opcode.SPUT_OBJECT) - val endScreenReference = - getInstruction(endScreenReferenceIndex).reference.toString() - val enumMethod = reelEnumStaticFingerprint.methodOrThrow(reelEnumConstructorFingerprint) + getInstruction(endScreenReferenceIndex).reference.toString() + } + + lateinit var insertMethod: MutableMethod + var insertMethodFound = false + + if (is_18_49_or_greater) { + insertMethod = reelPlaybackRepeatFingerprint.methodOrThrow() + } else { + val isInsertMethod: Method.() -> Boolean = { + parameters.size == 1 && + parameterTypes.first().startsWith("L") && + returnType == "V" && + indexOfFirstInstruction { + getReference()?.toString() == endScreenReference + } >= 0 + } classes.forEach { classDef -> - classDef.methods.filter { method -> - method.parameters.size == 1 && - method.parameters[0].startsWith("L") && - method.returnType == "V" && - method.indexOfFirstInstruction { - getReference()?.toString() == endScreenReference - } >= 0 - }.forEach { targetMethod -> - proxy(classDef) - .mutableClass - .findMutableMethodOf(targetMethod) - .apply { - implementation!!.instructions - .withIndex() - .filter { (_, instruction) -> - val reference = - (instruction as? ReferenceInstruction)?.reference - reference is MethodReference && - MethodUtil.methodSignaturesMatch(enumMethod, reference) - } - .map { (index, _) -> index } - .reversed() - .forEach { index -> - val register = - getInstruction(index + 1).registerA - - addInstructions( - index + 2, """ - invoke-static {v$register}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; - move-result-object v$register - """ - ) - } + if (!insertMethodFound) { + classDef.methods.forEach { method -> + if (method.isInsertMethod()) { + insertMethodFound = true + insertMethod = proxy(classDef) + .mutableClass + .findMutableMethodOf(method) } + } } } } + val enumMethod = reelEnumStaticFingerprint.methodOrThrow(reelEnumConstructorFingerprint) + + insertMethod.apply { + implementation!!.instructions + .withIndex() + .filter { (_, instruction) -> + val reference = + (instruction as? ReferenceInstruction)?.reference + reference is MethodReference && + MethodUtil.methodSignaturesMatch(enumMethod, reference) + } + .map { (index, _) -> index } + .reversed() + .forEach { index -> + val register = + getInstruction(index + 1).registerA + + addInstructions( + index + 2, """ + invoke-static {v$register}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->changeShortsRepeatBehavior(Ljava/lang/Enum;)Ljava/lang/Enum; + move-result-object v$register + """ + ) + } + } + + // As of YouTube 20.09, Google has removed the code for 'Autoplay' and 'Pause' from this method. + // Manually add the 'Autoplay' code that Google removed. + // Tested on YouTube 20.10. + // TODO: add the 'Pause' code that Google removed. + if (is_20_09_or_greater) { + val (directReference, virtualReference) = with (reelPlaybackFingerprint.methodOrThrow(videoIdFingerprintShorts)) { + val directIndex = indexOfInitializationInstruction(this) + val virtualIndex = indexOfFirstInstructionOrThrow(directIndex) { + opcode == Opcode.INVOKE_VIRTUAL && + getReference()?.parameterTypes?.size == 1 + } + + Pair( + getInstruction(directIndex).reference as MethodReference, + getInstruction(virtualIndex).reference as MethodReference + ) + } + + insertMethod.apply { + val extensionIndex = indexOfFirstInstructionOrThrow { + opcode == Opcode.INVOKE_STATIC && + getReference()?.definingClass == EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR + } + val enumRegister = getInstruction(extensionIndex + 1).registerA + val freeIndex = indexOfFirstInstructionOrThrow(extensionIndex) { + opcode == Opcode.SGET_OBJECT && + getReference()?.name != "a" + } + val freeRegister = getInstruction(freeIndex).registerA + val getIndex = indexOfFirstInstructionOrThrow(extensionIndex) { + val reference = getReference() + opcode == Opcode.IGET_OBJECT && + reference?.definingClass == definingClass && + reference.type == virtualReference.definingClass + } + val getReference = getInstruction(getIndex).reference + + addInstructionsWithLabels( + extensionIndex + 2, """ + invoke-static {v$enumRegister}, $EXTENSION_REPEAT_STATE_CLASS_DESCRIPTOR->isAutoPlay(Ljava/lang/Enum;)Z + move-result v$freeRegister + if-eqz v$freeRegister, :ignore + new-instance v0, ${directReference.definingClass} + const/4 v1, 0x3 + const/4 v2, 0x0 + invoke-direct {v0, v1, v2, v2}, $directReference + iget-object v3, p0, $getReference + invoke-virtual {v3, v0}, $virtualReference + return-void + :ignore + nop + """ + ) + } + } else { + getContext().apply { + addEntryValues( + "revanced_change_shorts_repeat_state_entries", + "@string/revanced_change_shorts_repeat_state_entry_pause", + ) + addEntryValues( + "revanced_change_shorts_repeat_state_entry_values", + "END_SCREEN", + ) + } + } + if (is_19_34_or_greater) { shortsHUDFeatureFingerprint.injectLiteralInstructionBooleanCall( SHORTS_HUD_FEATURE_FLAG, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt index 7488b3d1e..6792d46bf 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/Fingerprints.kt @@ -16,6 +16,7 @@ import app.revanced.patches.youtube.utils.resourceid.totalTime import app.revanced.patches.youtube.utils.resourceid.varispeedUnavailableTitle import app.revanced.patches.youtube.utils.resourceid.videoQualityBottomSheet import app.revanced.patches.youtube.utils.sponsorblock.sponsorBlockBytecodePatch +import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -23,6 +24,7 @@ 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.FieldReference import com.android.tools.smali.dexlib2.iface.reference.MethodReference internal val bottomSheetMenuItemBuilderFingerprint = legacyFingerprint( @@ -219,6 +221,29 @@ internal val videoEndFingerprint = legacyFingerprint( literals = listOf(45368273L), ) +/** + * This fingerprint is compatible with all versions of YouTube starting from v18.29.38 to supported versions. + * This method is invoked only in Shorts. + * Accurate video information is invoked even when the user moves Shorts upward or downward. + */ +internal val videoIdFingerprintShorts = legacyFingerprint( + name = "videoIdFingerprintShorts", + returnType = "V", + parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), + opcodes = listOf( + Opcode.INVOKE_INTERFACE, + Opcode.MOVE_RESULT_OBJECT + ), + customFingerprint = custom@{ method, _ -> + if (method.containsLiteralInstruction(45365621L)) + return@custom true + + method.indexOfFirstInstruction { + getReference()?.name == "reelWatchEndpoint" + } >= 0 + } +) + /** * Several instructions are added to this method by different patches. * Therefore, patches using this fingerprint should not use the [Opcode] pattern, diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt index bdbf30c72..bb4baa8da 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/utils/playservice/VersionCheckPatch.kt @@ -65,6 +65,8 @@ var is_20_03_or_greater = false private set var is_20_05_or_greater = false private set +var is_20_09_or_greater = false + private set var is_20_10_or_greater = false private set @@ -112,6 +114,7 @@ val versionCheckPatch = resourcePatch( is_20_02_or_greater = 250299000 <= playStoreServicesVersion is_20_03_or_greater = 250405000 <= playStoreServicesVersion is_20_05_or_greater = 250605000 <= playStoreServicesVersion + is_20_09_or_greater = 251006000 <= playStoreServicesVersion is_20_10_or_greater = 251105000 <= playStoreServicesVersion } } diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt index 9c2e47803..c8648d9d9 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/Fingerprints.kt @@ -3,7 +3,6 @@ package app.revanced.patches.youtube.video.information import app.revanced.patches.youtube.utils.PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR import app.revanced.patches.youtube.utils.resourceid.notificationBigPictureIconWidth import app.revanced.patches.youtube.utils.resourceid.qualityAuto -import app.revanced.util.containsLiteralInstruction import app.revanced.util.fingerprint.legacyFingerprint import app.revanced.util.getReference import app.revanced.util.indexOfFirstInstruction @@ -133,29 +132,6 @@ fun indexOfPlayerResponseModelInterfaceInstruction(methodDef: Method) = getReference()?.definingClass == PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR } -/** - * This fingerprint is compatible with all versions of YouTube starting from v18.29.38 to supported versions. - * This method is invoked only in Shorts. - * Accurate video information is invoked even when the user moves Shorts upward or downward. - */ -internal val videoIdFingerprintShorts = legacyFingerprint( - name = "videoIdFingerprintShorts", - returnType = "V", - parameters = listOf(PLAYER_RESPONSE_MODEL_CLASS_DESCRIPTOR), - opcodes = listOf( - Opcode.INVOKE_INTERFACE, - Opcode.MOVE_RESULT_OBJECT - ), - customFingerprint = custom@{ method, _ -> - if (method.containsLiteralInstruction(45365621L)) - return@custom true - - method.indexOfFirstInstruction { - getReference()?.name == "reelWatchEndpoint" - } >= 0 - } -) - internal val videoQualityListFingerprint = legacyFingerprint( name = "videoQualityListFingerprint", returnType = "V", diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt index fa9d1075c..52683bdd5 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/information/VideoInformationPatch.kt @@ -19,6 +19,7 @@ import app.revanced.patches.youtube.utils.extension.Constants.SHARED_PATH import app.revanced.patches.youtube.utils.playertype.playerTypeHookPatch import app.revanced.patches.youtube.utils.resourceid.sharedResourceIdPatch import app.revanced.patches.youtube.utils.videoEndFingerprint +import app.revanced.patches.youtube.utils.videoIdFingerprintShorts import app.revanced.patches.youtube.video.playerresponse.Hook import app.revanced.patches.youtube.video.playerresponse.addPlayerResponseMethodHook import app.revanced.patches.youtube.video.playerresponse.playerResponseMethodHookPatch diff --git a/patches/src/main/resources/youtube/settings/host/values/arrays.xml b/patches/src/main/resources/youtube/settings/host/values/arrays.xml index 9ca518f23..cfb11f881 100644 --- a/patches/src/main/resources/youtube/settings/host/values/arrays.xml +++ b/patches/src/main/resources/youtube/settings/host/values/arrays.xml @@ -98,13 +98,11 @@ @string/revanced_change_shorts_repeat_state_entry_default @string/revanced_change_shorts_repeat_state_entry_repeat @string/revanced_change_shorts_repeat_state_entry_auto_play - @string/revanced_change_shorts_repeat_state_entry_pause UNKNOWN REPEAT SINGLE_PLAY - END_SCREEN @string/quality_auto