From 1adbd563b21b84bf19cb9eb87bb76bef00d40316 Mon Sep 17 00:00:00 2001 From: LisoUseInAIKyrios <118716522+LisoUseInAIKyrios@users.noreply.github.com> Date: Thu, 31 Jul 2025 14:27:17 -0400 Subject: [PATCH] fix(YouTube - Video quality): Use 1080p enhanced bitrate for Premium users (#5565) --- .../quality/RememberVideoQualityPatch.java | 128 ++++++++----- .../innertube/model/media/VideoQuality.java | 12 ++ .../patches/youtube/shared/Fingerprints.kt | 2 +- .../youtube/video/information/Fingerprints.kt | 4 +- .../information/VideoInformationPatch.kt | 4 +- .../youtube/video/quality/Fingerprints.kt | 32 +++- .../quality/RememberVideoQualityPatch.kt | 175 +++++++++++++----- 7 files changed, 256 insertions(+), 101 deletions(-) create mode 100644 extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java diff --git a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java index 82e75058b..59d76324e 100644 --- a/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java +++ b/extensions/youtube/src/main/java/app/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch.java @@ -5,9 +5,9 @@ import static app.revanced.extension.shared.Utils.NetworkType; import androidx.annotation.Nullable; -import java.lang.reflect.Field; -import java.lang.reflect.Method; -import java.util.ArrayList; +import com.google.android.libraries.youtube.innertube.model.media.VideoQuality; + +import java.util.Arrays; import java.util.List; import app.revanced.extension.shared.Logger; @@ -20,6 +20,14 @@ import app.revanced.extension.youtube.shared.ShortsPlayerState; @SuppressWarnings("unused") public class RememberVideoQualityPatch { + + /** + * Interface to use obfuscated methods. + */ + public interface VideoQualityMenuInterface { + void patch_setMenuIndexFromQuality(VideoQuality quality); + } + private static final int AUTOMATIC_VIDEO_QUALITY_VALUE = -2; private static final IntegerSetting videoQualityWifi = Settings.VIDEO_QUALITY_DEFAULT_WIFI; private static final IntegerSetting videoQualityMobile = Settings.VIDEO_QUALITY_DEFAULT_MOBILE; @@ -30,7 +38,8 @@ public class RememberVideoQualityPatch { /** * If the user selected a new quality from the flyout menu, - * and {@link Settings#REMEMBER_VIDEO_QUALITY_LAST_SELECTED} is enabled. + * and {@link Settings#REMEMBER_VIDEO_QUALITY_LAST_SELECTED} + * or {@link Settings#REMEMBER_SHORTS_QUALITY_LAST_SELECTED} is enabled. */ private static boolean userChangedDefaultQuality; @@ -40,10 +49,10 @@ public class RememberVideoQualityPatch { private static int userSelectedQualityIndex; /** - * The available qualities of the current video in human readable form: [1080, 720, 480] + * The available qualities of the current video. */ @Nullable - private static List videoQualities; + private static List videoQualities; private static boolean shouldRememberVideoQuality() { BooleanSetting preference = ShortsPlayerState.isOpen() ? @@ -52,23 +61,27 @@ public class RememberVideoQualityPatch { return preference.get(); } - private static void changeDefaultQuality(int defaultQuality) { + private static void changeDefaultQuality(int qualityResolution) { + final boolean shortPlayerOpen = ShortsPlayerState.isOpen(); String networkTypeMessage; - boolean useShortsPreference = ShortsPlayerState.isOpen(); + IntegerSetting qualitySetting; if (Utils.getNetworkType() == NetworkType.MOBILE) { - if (useShortsPreference) shortsQualityMobile.save(defaultQuality); - else videoQualityMobile.save(defaultQuality); networkTypeMessage = str("revanced_remember_video_quality_mobile"); + qualitySetting = shortPlayerOpen ? shortsQualityMobile : videoQualityMobile; } else { - if (useShortsPreference) shortsQualityWifi.save(defaultQuality); - else videoQualityWifi.save(defaultQuality); networkTypeMessage = str("revanced_remember_video_quality_wifi"); + qualitySetting = shortPlayerOpen ? shortsQualityWifi : videoQualityWifi; } + qualitySetting.save(qualityResolution); + if (Settings.REMEMBER_VIDEO_QUALITY_LAST_SELECTED_TOAST.get()) Utils.showToastShort(str( - useShortsPreference ? "revanced_remember_video_quality_toast_shorts" : "revanced_remember_video_quality_toast", - networkTypeMessage, (defaultQuality + "p") - )); + shortPlayerOpen + ? "revanced_remember_video_quality_toast_shorts" + : "revanced_remember_video_quality_toast", + networkTypeMessage, + (qualityResolution + "p")) + ); } /** @@ -77,9 +90,11 @@ public class RememberVideoQualityPatch { * @param qualities Video qualities available, ordered from largest to smallest, with index 0 being the 'automatic' value of -2 * @param originalQualityIndex quality index to use, as chosen by YouTube */ - public static int setVideoQuality(Object[] qualities, final int originalQualityIndex, Object qInterface, String qIndexMethod) { + public static int setVideoQuality(VideoQuality[] qualities, VideoQualityMenuInterface menu, int originalQualityIndex) { try { - boolean useShortsPreference = ShortsPlayerState.isOpen(); + Utils.verifyOnMainThread(); + + final boolean useShortsPreference = ShortsPlayerState.isOpen(); final int preferredQuality = Utils.getNetworkType() == NetworkType.MOBILE ? (useShortsPreference ? shortsQualityMobile : videoQualityMobile).get() : (useShortsPreference ? shortsQualityWifi : videoQualityWifi).get(); @@ -89,16 +104,8 @@ public class RememberVideoQualityPatch { } if (videoQualities == null || videoQualities.size() != qualities.length) { - videoQualities = new ArrayList<>(qualities.length); - for (Object streamQuality : qualities) { - for (Field field : streamQuality.getClass().getFields()) { - if (field.getType().isAssignableFrom(Integer.TYPE) - && field.getName().length() <= 2) { - videoQualities.add(field.getInt(streamQuality)); - } - } - } - + videoQualities = Arrays.asList(qualities); + // After changing videos the qualities can initially be for the prior video. // So if the qualities have changed an update is needed. qualityNeedsUpdating = true; @@ -107,9 +114,9 @@ public class RememberVideoQualityPatch { if (userChangedDefaultQuality) { userChangedDefaultQuality = false; - final int quality = videoQualities.get(userSelectedQualityIndex); + VideoQuality quality = videoQualities.get(userSelectedQualityIndex); Logger.printDebug(() -> "User changed default quality to: " + quality); - changeDefaultQuality(quality); + changeDefaultQuality(quality.patch_getResolution()); return userSelectedQualityIndex; } @@ -119,65 +126,86 @@ public class RememberVideoQualityPatch { qualityNeedsUpdating = false; // Find the highest quality that is equal to or less than the preferred. - int qualityToUse = videoQualities.get(0); // first element is automatic mode + VideoQuality qualityToUse = videoQualities.get(0); // First element is automatic mode. int qualityIndexToUse = 0; int i = 0; - for (Integer quality : videoQualities) { - if (quality <= preferredQuality && qualityToUse < quality) { + for (VideoQuality quality : videoQualities) { + final int qualityResolution = quality.patch_getResolution(); + if (qualityResolution > qualityToUse.patch_getResolution() && qualityResolution <= preferredQuality) { qualityToUse = quality; qualityIndexToUse = i; + break; } i++; } // If the desired quality index is equal to the original index, // then the video is already set to the desired default quality. - final int qualityToUseFinal = qualityToUse; + String qualityToUseName = qualityToUse.patch_getQualityName(); if (qualityIndexToUse == originalQualityIndex) { - // On first load of a new video, if the UI video quality flyout menu - // is not updated then it will still show 'Auto' (ie: Auto (480p)), - // even though it's already set to the desired resolution. - // - // To prevent confusion, set the video index anyways (even if it matches the existing index) - // as that will force the UI picker to not display "Auto". - Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseFinal); + Logger.printDebug(() -> "Video is already preferred quality: " + qualityToUseName); } else { Logger.printDebug(() -> "Changing video quality from: " - + videoQualities.get(originalQualityIndex) + " to: " + qualityToUseFinal); + + videoQualities.get(originalQualityIndex).patch_getQualityName() + + " to: " + qualityToUseName); } - Method m = qInterface.getClass().getMethod(qIndexMethod, Integer.TYPE); - m.invoke(qInterface, qualityToUse); + // On first load of a new video, if the video is already the desired quality + // then the quality flyout will show 'Auto' (ie: Auto (720p)). + // + // To prevent user confusion, set the video index even if the + // quality is already correct so the UI picker will not display "Auto". + menu.patch_setMenuIndexFromQuality(qualities[qualityIndexToUse]); + return qualityIndexToUse; } catch (Exception ex) { - Logger.printException(() -> "Failed to set quality", ex); + Logger.printException(() -> "setVideoQuality failure", ex); return originalQualityIndex; } } /** - * Injection point. Old quality menu. + * Injection point. Fixes bad data used by YouTube. */ - public static void userChangedQuality(int selectedQualityIndex) { + public static int fixVideoQualityResolution(String name, int quality) { + final int correctQuality = 480; + if (name.equals("480p") && quality != correctQuality) { + Logger.printDebug(() -> "Fixing bad data of " + name + " from: " + quality + + " to: " + correctQuality); + return correctQuality; + } + + return quality; + } + + /** + * Injection point. + * @param qualityIndex Element index of {@link #videoQualities}. + */ + public static void userChangedQuality(int qualityIndex) { if (shouldRememberVideoQuality()) { - userSelectedQualityIndex = selectedQualityIndex; + userSelectedQualityIndex = qualityIndex; userChangedDefaultQuality = true; } } /** - * Injection point. New quality menu. + * Injection point. + * @param videoResolution Human readable resolution: 480, 720, 1080. */ - public static void userChangedQualityInNewFlyout(int selectedQuality) { + public static void userChangedQualityInFlyout(int videoResolution) { + Utils.verifyOnMainThread(); if (!shouldRememberVideoQuality()) return; - changeDefaultQuality(selectedQuality); // Quality is human readable resolution (ie: 1080). + changeDefaultQuality(videoResolution); } /** * Injection point. */ public static void newVideoStarted(VideoInformation.PlaybackController ignoredPlayerController) { + Utils.verifyOnMainThread(); + Logger.printDebug(() -> "newVideoStarted"); qualityNeedsUpdating = true; videoQualities = null; diff --git a/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java b/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java new file mode 100644 index 000000000..67dbc6873 --- /dev/null +++ b/extensions/youtube/stub/src/main/java/com/google/android/libraries/youtube/innertube/model/media/VideoQuality.java @@ -0,0 +1,12 @@ +package com.google.android.libraries.youtube.innertube.model.media; + +public class VideoQuality { + public final String patch_getQualityName() { + throw new UnsupportedOperationException("Stub"); + } + + public final int patch_getResolution() { + throw new UnsupportedOperationException("Stub"); + } +} + diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt index 82eb0f7a9..4a9f3a020 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/shared/Fingerprints.kt @@ -121,7 +121,7 @@ internal val subtitleButtonControllerFingerprint = fingerprint { ) } -internal val newVideoQualityChangedFingerprint = fingerprint { +internal val videoQualityChangedFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("L") parameters("L") 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 5157f9823..7d711aec8 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 @@ -1,7 +1,7 @@ package app.revanced.patches.youtube.video.information import app.revanced.patcher.fingerprint -import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint +import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint import app.revanced.util.getReference import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode @@ -110,7 +110,7 @@ internal val seekRelativeFingerprint = fingerprint { } /** - * Resolves with the class found in [newVideoQualityChangedFingerprint]. + * Resolves with the class found in [videoQualityChangedFingerprint]. */ internal val playbackSpeedMenuSpeedChangedFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) 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 5a9c5e740..99f0c75de 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 @@ -9,7 +9,7 @@ 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.youtube.misc.extension.sharedExtensionPatch -import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint +import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint import app.revanced.patches.youtube.video.playerresponse.Hook import app.revanced.patches.youtube.video.playerresponse.addPlayerResponseMethodHook import app.revanced.patches.youtube.video.playerresponse.playerResponseMethodHookPatch @@ -263,7 +263,7 @@ val videoInformationPatch = bytecodePatch( // Handle new playback speed menu. playbackSpeedMenuSpeedChangedFingerprint.match( - newVideoQualityChangedFingerprint.originalClassDef, + videoQualityChangedFingerprint.originalClassDef, ).method.apply { val index = indexOfFirstInstructionOrThrow(Opcode.IGET) diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt index 5ce6da9c1..c98bdac63 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/Fingerprints.kt @@ -5,6 +5,21 @@ import app.revanced.util.literal import com.android.tools.smali.dexlib2.AccessFlags import com.android.tools.smali.dexlib2.Opcode +internal const val YOUTUBE_VIDEO_QUALITY_CLASS_TYPE = "Lcom/google/android/libraries/youtube/innertube/model/media/VideoQuality;" + +internal val videoQualityFingerprint = fingerprint { + accessFlags(AccessFlags.PUBLIC, AccessFlags.CONSTRUCTOR) + parameters( + "I", // Resolution. + "Ljava/lang/String;", // Human readable resolution: "480p", "1080p Premium", etc + "Z", + "L" + ) + custom { _, classDef -> + classDef.type == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE + } +} + /** * Matches with the class found in [videoQualitySetterFingerprint]. */ @@ -23,6 +38,22 @@ internal val videoQualityItemOnClickParentFingerprint = fingerprint { strings("VIDEO_QUALITIES_MENU_BOTTOM_SHEET_FRAGMENT") } +/** + * Resolves to class found in [videoQualityItemOnClickFingerprint]. + */ +internal val videoQualityItemOnClickFingerprint = fingerprint { + returns("V") + parameters( + "Landroid/widget/AdapterView;", + "Landroid/view/View;", + "I", + "J" + ) + custom { method, _ -> + method.name == "onItemClick" + } +} + internal val videoQualitySetterFingerprint = fingerprint { accessFlags(AccessFlags.PUBLIC, AccessFlags.FINAL) returns("V") @@ -37,7 +68,6 @@ internal val videoQualitySetterFingerprint = fingerprint { strings("menu_item_video_quality") } - internal val videoQualityMenuOptionsFingerprint = fingerprint { accessFlags(AccessFlags.STATIC) returns("[L") diff --git a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt index 79e2fade4..1e42048ba 100644 --- a/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt +++ b/patches/src/main/kotlin/app/revanced/patches/youtube/video/quality/RememberVideoQualityPatch.kt @@ -3,8 +3,8 @@ package app.revanced.patches.youtube.video.quality 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.util.proxy.mutableTypes.MutableMethod.Companion.toMutable import app.revanced.patches.all.misc.resources.addResources import app.revanced.patches.all.misc.resources.addResourcesPatch import app.revanced.patches.shared.misc.settings.preference.ListPreference @@ -12,15 +12,21 @@ import app.revanced.patches.shared.misc.settings.preference.SwitchPreference import app.revanced.patches.youtube.misc.extension.sharedExtensionPatch import app.revanced.patches.youtube.misc.playertype.playerTypeHookPatch import app.revanced.patches.youtube.misc.settings.settingsPatch -import app.revanced.patches.youtube.shared.newVideoQualityChangedFingerprint +import app.revanced.patches.youtube.shared.videoQualityChangedFingerprint import app.revanced.patches.youtube.video.information.onCreateHook import app.revanced.patches.youtube.video.information.videoInformationPatch +import com.android.tools.smali.dexlib2.AccessFlags +import com.android.tools.smali.dexlib2.builder.MutableMethodImplementation 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 com.android.tools.smali.dexlib2.immutable.ImmutableMethod +import com.android.tools.smali.dexlib2.immutable.ImmutableMethodParameter private const val EXTENSION_CLASS_DESCRIPTOR = "Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch;" +private const val EXTENSION_VIDEO_QUALITY_MENU_INTERFACE = + "Lapp/revanced/extension/youtube/patches/playback/quality/RememberVideoQualityPatch\$VideoQualityMenuInterface;" val rememberVideoQualityPatch = bytecodePatch { dependsOn( @@ -70,72 +76,151 @@ val rememberVideoQualityPatch = bytecodePatch { */ onCreateHook(EXTENSION_CLASS_DESCRIPTOR, "newVideoStarted") + videoQualityFingerprint.let { + // Fix bad data used by YouTube. + it.method.addInstructions( + 0, + """ + invoke-static { p2, p1 }, $EXTENSION_CLASS_DESCRIPTOR->fixVideoQualityResolution(Ljava/lang/String;I)I + move-result p1 + """ + ) + + // Add methods to access obfuscated quality fields. + it.classDef.apply { + methods.add( + ImmutableMethod( + type, + "patch_getQualityName", + listOf(), + "Ljava/lang/String;", + AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, + null, + null, + MutableMethodImplementation(2), + ).toMutable().apply { + // Only one string field. + val qualityNameField = fields.single { field -> + field.type == "Ljava/lang/String;" + } + + addInstructions( + 0, + """ + iget-object v0, p0, $qualityNameField + return-object v0 + """ + ) + } + ) + + methods.add( + ImmutableMethod( + type, + "patch_getResolution", + listOf(), + "I", + AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, + null, + null, + MutableMethodImplementation(2), + ).toMutable().apply { + val resolutionField = fields.single { field -> + field.type == "I" + } + + addInstructions( + 0, + """ + iget v0, p0, $resolutionField + return v0 + """ + ) + } + ) + } + } + // Inject a call to set the remembered quality once a video loads. setQualityByIndexMethodClassFieldReferenceFingerprint.match( - videoQualitySetterFingerprint.originalClassDef, + videoQualitySetterFingerprint.originalClassDef ).let { match -> // This instruction refers to the field with the type that contains the setQualityByIndex method. val instructions = match.method.implementation!!.instructions - - val getOnItemClickListenerClassReference = + val onItemClickListenerClassReference = (instructions.elementAt(0) as ReferenceInstruction).reference - val getSetQualityByIndexMethodClassFieldReference = - (instructions.elementAt(1) as ReferenceInstruction).reference + val setQualityFieldReference = + ((instructions.elementAt(1) as ReferenceInstruction).reference) as FieldReference - val setQualityByIndexMethodClassFieldReference = - getSetQualityByIndexMethodClassFieldReference as FieldReference + proxy( + classes.find { classDef -> + classDef.type == setQualityFieldReference.type + }!! + ).mutableClass.apply { + // Add interface and helper methods to allow extension code to call obfuscated methods. + interfaces.add(EXTENSION_VIDEO_QUALITY_MENU_INTERFACE) - val setQualityByIndexMethodClass = classes - .find { classDef -> classDef.type == setQualityByIndexMethodClassFieldReference.type }!! + // Get the name of the setQualityByIndex method. + val setQualityMenuIndexMethod = methods.single { + method -> method.parameterTypes.firstOrNull() == YOUTUBE_VIDEO_QUALITY_CLASS_TYPE + } - // Get the name of the setQualityByIndex method. - val setQualityByIndexMethod = setQualityByIndexMethodClass.methods - .find { method -> method.parameterTypes.first() == "I" } - ?: throw PatchException("Could not find setQualityByIndex method") + methods.add( + ImmutableMethod( + type, + "patch_setMenuIndexFromQuality", + listOf( + ImmutableMethodParameter(YOUTUBE_VIDEO_QUALITY_CLASS_TYPE, null, null) + ), + "V", + AccessFlags.PUBLIC.value or AccessFlags.FINAL.value, + null, + null, + MutableMethodImplementation(2), + ).toMutable().apply { + addInstructions( + 0, + """ + invoke-virtual { p0, p1 }, $setQualityMenuIndexMethod + return-void + """ + ) + } + ) + } videoQualitySetterFingerprint.method.addInstructions( 0, """ # Get the object instance to invoke the setQualityByIndex method on. - iget-object v0, p0, $getOnItemClickListenerClassReference - iget-object v0, v0, $getSetQualityByIndexMethodClassFieldReference + iget-object v0, p0, $onItemClickListenerClassReference + iget-object v0, v0, $setQualityFieldReference - # Get the method name. - const-string v1, "${setQualityByIndexMethod.name}" - - # Set the quality. - # The first parameter is the array list of video qualities. - # The second parameter is the index of the selected quality. - # The register v0 stores the object instance to invoke the setQualityByIndex method on. - # The register v1 stores the name of the setQualityByIndex method. - invoke-static { p1, p2, v0, v1 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([Ljava/lang/Object;ILjava/lang/Object;Ljava/lang/String;)I + invoke-static { p1, v0, p2 }, $EXTENSION_CLASS_DESCRIPTOR->setVideoQuality([$YOUTUBE_VIDEO_QUALITY_CLASS_TYPE${EXTENSION_VIDEO_QUALITY_MENU_INTERFACE}I)I move-result p2 - """, + """ ) } // Inject a call to remember the selected quality. - videoQualityItemOnClickParentFingerprint.classDef.methods.find { it.name == "onItemClick" } - ?.apply { - val listItemIndexParameter = 3 + videoQualityItemOnClickFingerprint.match( + videoQualityItemOnClickParentFingerprint.classDef + ).method.addInstruction( + 0, + "invoke-static { p3 }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V" + ) + + // Inject a call to remember the user selected quality. + videoQualityChangedFingerprint.let { + it.method.apply { + val index = it.patternMatch!!.startIndex + val register = getInstruction(index).registerA addInstruction( - 0, - "invoke-static { p$listItemIndexParameter }, " + - "$EXTENSION_CLASS_DESCRIPTOR->userChangedQuality(I)V", + index + 1, + "invoke-static { v$register }, $EXTENSION_CLASS_DESCRIPTOR->userChangedQualityInFlyout(I)V", ) - } ?: throw PatchException("Failed to find onItemClick method") - - // Remember video quality if not using old layout menu. - newVideoQualityChangedFingerprint.method.apply { - val index = newVideoQualityChangedFingerprint.patternMatch!!.startIndex - val qualityRegister = getInstruction(index).registerA - - addInstruction( - index + 1, - "invoke-static { v$qualityRegister }, " + - "$EXTENSION_CLASS_DESCRIPTOR->userChangedQualityInNewFlyout(I)V", - ) + } } } }